Обход каталогов

Sep 03, 2010 08:40

А все таки, в языке Си есть свое очарование. Да, пускай это высокоуровневый ассемблер, да на нем приходится писать много кода для простых вещей, да это не оружие массового поражения, вроде C++, но в его простоте и заключается красота. Это как меч, - им достаточно сложно научиться пользоваться, в отличие например от пистолета или автомата, но если научился, то испытываешь ни с чем несравнимое удовольствие от обращения с ним.

На досуге ковыряюсь с проектом небольших консольных приложений, хэш калькуляторов (есть MD4, MD5, SHA1, SHA256, SHA384, SHA512 и Whirlpool), и там есть функция обхода каталогов с фильтрацией (исключающей и включающей). Код достаточно независимый от контекста, может кому и пригодится. Да, замечание - там используется Apache Portable Runtime. Это отличная и легковесная библиотека, написанная целиком на Си. Есть конечно в ней ряд странностей, например методика управления памятью только через собственные пулы, но это не самое страшное . Впрочем, такой дизайн наверняка был оправдан в самом начале проектирования библиотеки. Также, стоит учесть её специфику - создавалась для проекта веб сервера.

Функционал простой. TraverseDirectory обходит каталоги, с возможностью рекурсивного обхода и фильтрации. Для каждого файла может вызывается обработчик, указатель на который задается в структуре TraverseContext. Можно задавать несколько шаблонов фильтрации разделяя их точкой с запятой. Обратите внимание сколько много кода, на каком-нибудь C# кода будет раз в пять меньше, для того же самого :). Итак сам код:



#include

#include "apr_pools.h"
#include "apr_strings.h"
#include "apr_file_io.h"
#include "apr_fnmatch.h"

#define ERROR_BUFFER_SIZE 2 * 1024
#define PATTERN_SEPARATOR ";"

/*!
* Structure to store file handler information.
*/
typedef struct DataContext {
// context defs...
} DataContext;

typedef struct TraverseContext {
int IsScanDirRecursively;
const char* ExcludePattern;
const char* IncludePattern;
void (* PfnFileHandler)(apr_pool_t* pool, const char* pathToFile, DataContext* ctx);
DataContext* DataCtx;
} TraverseContext;

/*!
* \brief Try to match the string to the given pattern using apr_fnmatch function.
* Several patterns must be separated by ; Matching is case insensitive
* PATTERN: Backslash followed by any character, including another
* backslash.

* MATCHES: That character exactly.
*
*

* PATTERN: ?

* MATCHES: Any single character.
*

*
*

* PATTERN: *

* MATCHES: Any sequence of zero or more characters. (Note that multiple
* *s in a row are equivalent to one.)
*
* PATTERN: Any character other than \?*[ or a \ at the end of the pattern

* MATCHES: That character exactly. (Case sensitive.)
*
* PATTERN: [ followed by a class description followed by ]

* MATCHES: A single character described by the class description.
* (Never matches, if the class description reaches until the
* end of the string without a ].) If the first character of
* the class description is ^ or !, the sense of the description
* is reversed. The rest of the class description is a list of
* single characters or pairs of characters separated by -. Any
* of those characters can have a backslash in front of them,
* which is ignored; this lets you use the characters ] and -
* in the character class, as well as ^ and ! at the
* beginning. The pattern matches a single character if it
* is one of the listed characters or falls into one of the
* listed ranges (inclusive, case sensitive). Ranges with
* the first character larger than the second are legal but
* never match. Edge cases: [] never matches, and [^] and [!]
* always match without consuming a character.
*
* Note that these patterns attempt to match the entire string, not
* just find a substring matching the pattern.
*
* \param pool Apache pool
* \param str The string we are trying to match
* \param pattern The pattern to match to
* \return non-zero if the string matches to the pattern specified
*/
int MatchToCompositePattern(apr_pool_t* pool, const char* str, const char* pattern)
{
char* parts = NULL;
char* last = NULL;
char* p = NULL;

if (!pattern) {
return TRUE; // important
}
if (!str) {
return FALSE; // important
}

parts = apr_pstrdup(pool, pattern); /* strtok wants non-const data */
p = apr_strtok(parts, PATTERN_SEPARATOR, &last);
while (p) {
if (apr_fnmatch(p, str, APR_FNM_CASE_BLIND) == APR_SUCCESS) {
return TRUE;
}
p = apr_strtok(NULL, PATTERN_SEPARATOR, &last);
}
return FALSE;
}

void PrintError(apr_status_t status)
{
char errbuf[ERROR_BUFFER_SIZE];
apr_strerror(status, errbuf, ERROR_BUFFER_SIZE);
printf("%s\n", errbuf);
}

void TraverseDirectory(apr_pool_t* pool, const char* dir, TraverseContext* ctx)
{
apr_finfo_t info = { 0 };
apr_dir_t* d = NULL;
apr_status_t status = APR_SUCCESS;
char* fullPath = NULL; // Full path to file or subdirectory
apr_pool_t* filePool = NULL;
apr_pool_t* dirPool = NULL;

apr_pool_create(&filePool, pool);
apr_pool_create(&dirPool, pool);

status = apr_dir_open(&d, dir, dirPool);
if (status != APR_SUCCESS) {
PrintError(status);
goto cleanup;
}

for (;;) {
apr_pool_clear(filePool); // cleanup file allocated memory
status = apr_dir_read(&info, APR_FINFO_NAME | APR_FINFO_MIN, d);
if (APR_STATUS_IS_ENOENT(status)) {
break;
}
// Subdirectory handling code
if ((info.filetype == APR_DIR) && ctx->IsScanDirRecursively) {
// skip current and parent dir
if (((info.name[0] == '.') && (info.name[1] == '\0'))
|| ((info.name[0] == '.') && (info.name[1] == '.') && (info.name[2] == '\0'))) {
continue;
}

status = apr_filepath_merge(&fullPath,
dir,
info.name,
APR_FILEPATH_NATIVE,
filePool);
if (status != APR_SUCCESS) {
PrintError(status);
continue;
}
TraverseDirectory(pool, fullPath, ctx);
} // End subdirectory handling code

if ((status != APR_SUCCESS) || (info.filetype != APR_REG)) {
continue;
}

if (!MatchToCompositePattern(filePool, info.name, ctx->IncludePattern)) {
continue;
}
// IMPORTANT: check pointer here otherwise the logic will fail
if (ctx->ExcludePattern &&
MatchToCompositePattern(filePool, info.name, ctx->ExcludePattern)) {
continue;
}

status = apr_filepath_merge(&fullPath,
dir,
info.name,
APR_FILEPATH_NATIVE,
filePool);
if (status != APR_SUCCESS) {
PrintError(status);
continue;
}

ctx->PfnFileHandler(filePool, fullPath, ctx->DataCtx);
}

status = apr_dir_close(d);
if (status != APR_SUCCESS) {
PrintError(status);
}
cleanup:
apr_pool_destroy(dirPool);
apr_pool_destroy(filePool);
}
Пользоваться можно например так:

void FileHandler(apr_pool_t* pool, const char* pathToFile, DataContext* ctx)
{
// file handler implementation
}

int main(int argc, const char* const argv[])
{
TraverseContext dirContext = { 0 };
DataContext dataCtx = { 0 };
apr_pool_t* pool = NULL;
const char* dir = NULL; // dir to traverse

apr_app_initialize(&argc, &argv, NULL);
atexit(apr_terminate);
apr_pool_create(&pool, NULL);

dirContext.IsScanDirRecursively = 1 // set recursively scanning option
dirContext.ExcludePattern = NULL// set exclude pattern
dirContext.IncludePattern = NULL // set include pattern
dirContext.DataCtx = &dataCtx;
dirContext.PfnFileHandler = &FileHandler;

TraverseDirectory(pool, dir, &dirContext);

apr_pool_destroy(pool);
return 0;
}

Си

Previous post Next post
Up