From: Hamatoma Date: Fri, 6 Feb 2015 00:02:50 +0000 (+0100) Subject: bugfixing X-Git-Url: https://gitweb.hamatoma.de/?a=commitdiff_plain;h=d94271ec8a8e24efe5dc49954c78b63bef55a492;p=crepublib bugfixing --- diff --git a/base/ReByteBuffer.cpp b/base/ReByteBuffer.cpp index 724d7ef..ded7ee8 100644 --- a/base/ReByteBuffer.cpp +++ b/base/ReByteBuffer.cpp @@ -57,7 +57,7 @@ ReByteBuffer::ReByteBuffer(const Byte* source, size_t sourceLength) : // m_primaryBuffer m_buffer(m_primaryBuffer), m_length(0), - m_capacity(sizeof m_primaryBuffer), + m_capacity(sizeof m_primaryBuffer - 1), m_reallocation(0) { m_buffer[0] = '\0'; @@ -227,21 +227,21 @@ ReByteBuffer& ReByteBuffer::appendHexDump(const char* data, size_t length, if (withAscii){ if (separator != NULL) append(separator, -1); + char cc; + for (ix = 0; ix < bytesPerLine; ix++){ + if (ix < (int) length) + appendInt( (cc = data[ix]) < ' ' || cc > 127 ? '.' : cc, "%c"); + else + append(" "); + if (ix < bytesPerLine - 1 && gapBehind > 0 + && (gapBehind == 1 || ix % gapBehind == gapBehind - 1)) + append(" ", 1); + } + append("\n", 1); + length = length <= (size_t) bytesPerLine ? 0 : length - bytesPerLine; + data += bytesPerLine; + offset += bytesPerLine; } - char cc; - for (ix = 0; ix < bytesPerLine; ix++){ - if (ix < (int) length) - appendInt( (cc = data[ix]) < ' ' || cc > 127 ? '.' : cc, "%c"); - else - append(" "); - if (ix < bytesPerLine - 1 && gapBehind > 0 - && (gapBehind == 1 || ix % gapBehind == gapBehind - 1)) - append(" ", 1); - } - append("\n", 1); - length = length <= (size_t) bytesPerLine ? 0 : length - bytesPerLine; - data += bytesPerLine; - offset += bytesPerLine; } return *this; } diff --git a/base/ReHashList.cpp b/base/ReHashList.cpp index 123c3f9..a217277 100644 --- a/base/ReHashList.cpp +++ b/base/ReHashList.cpp @@ -206,6 +206,20 @@ void ReHashList::setCapacity(int maxKeys, int keySpace, int contentSpace){ m_keys.setCapacity(maxKeys, keySpace); m_values.setCapacity(maxKeys, contentSpace); } +/** + * Sets the sizes (maximal length) of the strings. + * + * @param sizeOfKeyString storage size of the key string:
+ * 1: maximal string length = 255,
+ * 2: maximal string length = 64k ... + * @param sizeOfValueString storage size of the key string:
+ * 1: maximal string length = 255,
+ * 2: maximal string length = 64k ... + */ +void ReHashList::setSizes(int sizeOfKeyString, int sizeOfValueString){ + m_keys.setSizes(4, sizeOfKeyString); + m_values.setSizes(0, sizeOfValueString); +} /** * Appends the status data of the instance onto the buffer. diff --git a/base/ReHashList.hpp b/base/ReHashList.hpp index bd0618a..f9cb183 100644 --- a/base/ReHashList.hpp +++ b/base/ReHashList.hpp @@ -44,6 +44,7 @@ public: void put(const char* key, const char* value); void put(const ReByteBuffer& key, const ReByteBuffer& value); void setCapacity(int maxKeys, int keySpace, int contentSpace); + void setSizes(int sizeOfKeyString, int sizeOfValueString); const char* status(ReByteBuffer& buffer, const char* prefix = NULL) const; protected: int find(const Byte* key, size_t length) const; diff --git a/base/ReProgramArgs.cpp b/base/ReProgramArgs.cpp index 7a2c2e7..1ef93f9 100644 --- a/base/ReProgramArgs.cpp +++ b/base/ReProgramArgs.cpp @@ -93,6 +93,7 @@ ReProgramArgs::ReProgramArgs(const char* usageString, const char* examples) m_program("?"), m_lastError() { + m_values.setSizes(1, 2); m_usage.split(usageString, '\n'); if (examples != NULL){ if (strstr(examples, "$0") == NULL) diff --git a/base/ReSeqArray.cpp b/base/ReSeqArray.cpp index 8b73fbb..efe1f8f 100644 --- a/base/ReSeqArray.cpp +++ b/base/ReSeqArray.cpp @@ -66,7 +66,7 @@ ReSeqArray::ReSeqArray(int deltaList, int deltaBuffer) : m_content(deltaBuffer), m_list(deltaList), m_lost(0), - m_entrySize(sizeof(Index) + 1 + 8), + m_entrySize(sizeof(Index) + 1 + sizeof (void*)), m_commonSize(INDIVIDUAL_SIZE), m_sizeOfTag(sizeof (void*)), m_sizeOfLength(1), @@ -462,7 +462,13 @@ void ReSeqArray::set(Index index, const Byte* source, indexContent = m_content.length(); m_content.append(source, sourceLength); } + printf("vor:\n%s\n", ReByteBuffer("") + .appendHexDump(m_list.str(), m_entrySize*(1+index), 0, + m_entrySize*2, "%4d: ", false, m_entrySize, m_entrySize).str()); setSequence(seq, indexContent, sourceLength, tag); + printf("dann:\n%s\n", ReByteBuffer("") + .appendHexDump(m_list.str(), m_entrySize*(1+index), 0, + m_entrySize*2, "%4d: ", false, m_entrySize, m_entrySize).str()); } } @@ -684,6 +690,7 @@ void ReSeqArray::setSizes(int sizeOfTag, int sizeOfLength, size_t constantSize){ constantSize = 0; } m_commonSize = constantSize; + m_entrySize = sizeof(Index) + m_sizeOfLength + m_sizeOfTag; } /** @brief Sorts the list. diff --git a/os/ReDirTools.cpp b/os/ReDirTools.cpp index f125b6b..11d9bce 100644 --- a/os/ReDirTools.cpp +++ b/os/ReDirTools.cpp @@ -1,589 +1,589 @@ -/* - * ReDirTools.cpp - * - * License: Public domain - * Do what you want. - * No warranties and disclaimer of any damages. - * The latest sources: https://github.com/republib - */ - -#include "base/rebase.hpp" -#include "os/reos.hpp" - -enum LOCATION_DIRTOOL { - LC_COPY_FILE_1 = LC_DIRTOOLS + 1, // 50101 - LC_COPY_FILE_2, // 50102 - LC_COPY_FILE_3, // 50103 - LC_COPY_FILE_4, // 50104 - LC_COPY_FILE_5, // 50105 - LC_COPY_FILE_6, // 50106 - LC_MAKE_DIR_1, // 50107 - LC_MAKE_DIR_2, // 50108 - LC_SET_PROPERTIES_1, // 50109 - LC_SET_PROPERTIES_2, // 50110 - LC_SET_PROPERTIES_3, // 50111 -}; -const char* ReDirTools::m_version = "2015.02.04"; - -static const char* s_helpSummary[] = { - "dirtool or dt ", - " Useful commands around directory trees.", - " Type 'dirtool help ' for more help.", - ":", - "batch produce output to handle the found files with a script", - "help shows info about the arguments/options", - "list shows the meta data of the selected files", - "statistic shows statistics about a direcctory tree", - "synchronize copies only modified or new files from", - " from a source directory tre to a target", - NULL -}; - -const char* s_batchUsage[] = { - ": batch", - " produces output usable for a batch file (script)", - " all found files can be processed with a given script", - " each line starts (usually) with a script name (see -c)", - " then it follows the full filename of the found file", - " use --arguments or --script to configure the output line", - NULL -}; -const char* s_batchExamples[] = { - "dirtool batch -cbackup.bat --basename-pattern=;*.txt;*.doc e:\\data", - "dirtool batch --type=r '--arguments=backup.sh !basename! !path!' usr etc", - NULL -}; - -const char* s_listUsage[] = { - ": list", - " lists the metadata (size, modification date ...) of the selected files", - NULL -}; -const char* s_listExamples[] = { - "dirtool list --min-size=10M e:\\data", - "dirtool list --type=f -y7d --size=10M -p;*.cpp;*.hpp;Makefile*;-*~ /home/data" , - NULL -}; - -static const char* s_statisticUsage[] = { - ":" - "st(atistic) [] []", - " shows a statistic about a directory tree", - " a directory path: relative or absolute", - " 0: only the summary of will be shown", - " 1: shows the summery of each subdir of and the total", - " n: shows the summery of each subdir until level and the total", - " default: 1", - ":", - NULL -}; -const char* s_statisticExamples[] = { - "dirtool st -q -t0 e:\\windows", - "dirtool statistic --quiet --kbyte --trace-interval=60 ../mail 2", - "dirtool stat -q --kbyte d:data 2", - NULL -}; - -const char* s_syncUsage[] = { - ":", - "sy(nchronize) [ ...] ", - " Synchronizes the content of a directory tree with another.", - " Newer or missing files will be copied", - " If a source name ends with the separator (/ in linux, \\ in win) the target", - " will be completed with the basename of the source.", - " Example:", - " 'sync home backup' copies the file home/x.txt to backup/home/x.txt", - " 'sync data/ backup/data2' copies the file home/x.txt to backup/data2/x.txt", - NULL -}; -const char* s_syncExamples[] = { - "dirtool sync --basename-pattern=;*.txt;*.doc e:\\data\\ d:\\backup\\data2", - "dirtool sync --type=r --max-size=1G usr etc /media/backup", - NULL -}; - -/** - * Constructor. - * - * @param usage a string vector with a message how to use the command - * @param example a string vector with some examples how to use the command - */ -ReDirOptions::ReDirOptions(const char* usage[], const char* examples[]) : - ReTraceUnit(INT_MAX, INT_MAX), - m_programArgs(usage, examples), - m_nodePatterns(), - m_pathPatterns(), - m_compoundUsage(NULL), - m_countCompoundUsage(0), - m_output(stdout), - m_verboseLevel(V_NORMAL) -{ - m_nodePatterns.setIgnoreCase(true); - m_pathPatterns.setIgnoreCase(true); -} -/** - * Destructor. - */ -ReDirOptions::~ReDirOptions(){ - close(); - delete[] m_compoundUsage; -} -/** - * Adds a usage component to the compound usage message list. - * @param usage a string vector containing a part of the usage message - */ -void ReDirOptions::addCompoundUsage(const char** usage){ - int start = 0; - while(m_compoundUsage[start] != NULL){ - if (++start >= m_countCompoundUsage) - assert(false); - } - for (int ix = 0; usage[ix] != NULL; ix++){ - if (start + ix > m_countCompoundUsage){ - assert(false); - break; - } - m_compoundUsage[start + ix] = usage[ix]; - } -} - -/** - * Adds the standard filter options. - */ -void ReDirOptions::addStandardFilterOptions(){ - // standard short options: D d O o P p T t v y Z z - m_programArgs.addInt("maxdepth", - i18n("the depth of the subdirectory is lower or equal \n" - "0: search is done only in the base directory"), - 'D', "max-depth", 512); - m_programArgs.addInt("mindepth", - i18n("the depth of the subdirectory is greater or equal \n" - "0: search is done in all subdirectories"), - 'd', "min-depth", 0); - m_programArgs.addString("output", - i18n("the name of the output file.\n" - "The output will written to this file instead of stdout"), - 'O', "output-file", false, NULL); - m_programArgs.addString("older", - i18n("the modification date is older than \n" - " is a date (e.g. 2015.02.17) or number followed by an unit\n" - "units: m(inutes) h(hours), d(days). Default: m(inutes)\n" - "examples: -o25 --older-than=30d -o24h -o2009.3.2/12:00 -o1999.01.01"), - 'o', "older-than", false, NULL); - m_programArgs.addString("pathpattern", - i18n("a list of patterns for the path (without basename)\n" - "the separator is the first character of the list\n" - "Each pattern can contain '*' as wildcard\n" - "If the first character is '-' the pattern is a 'not pattern':\n" - "A directory will be entered if at least one of the positive patterns\n" - "and none of the 'not patterns' matches\n" - "examples:\n" - "';music;pic*' enters music and xy/Music and PIC and pictures but not xy/pic and img\n" - "';*;-.git;.hg' ignores .git and xy/z/.git and .ht"), - 'P', "path-pattern", false, NULL); - m_programArgs.addString("nodepattern", - i18n("a list of patterns for the basename (name without path) separated by ';'\n" - "Each pattern can contain '*' as wildcard\n" - "If the first character is '-' the pattern is a 'not pattern':\n" - "A file will be found if at least one of the positive patterns and none\n" - "of the 'not patterns' matches\n" - "examples: '*.cpp;*.hpp;Make*' '*;-*.bak;-*~"), - 'p', "basename-pattern", false, NULL); - m_programArgs.addString("verbose", - i18n("verbose level: 0: no info, 1: summary only, 2: normal, 3: chatter mode, 4: debug"), - 'v', "verbose", false, "1"); - m_programArgs.addInt("trace", - i18n("all seconds the current path will be traced\n" - "0: no trace"), - 'T', "trace-interval", 0); - m_programArgs.addString("type", - i18n("the file type\n" - " is a list of values:\n" - ": b(lock) c(har) d(irectory) (l)i(nkdir) l(ink) o(ther)\n" - " p(ipe) s(ocket) r(egular)\n" - "-sets: S(pecial)=bcspo N(ondir)=Slr\n" - "examples: -td --type=dr -tNi"), - 't', "type", false, NULL); - m_programArgs.addString("younger", - i18n("the modification date is younger than \n" - " is a date (e.g. 2015.02.17) or number followed by an unit\n" - "units: m(inutes) h(hours), d(days). Default: m(inutes)"), - 'y', "younger-than", false, NULL); - m_programArgs.addString("maxsize", - i18n("the filesize is greater or equal \n" - " is a number followed by an unit\n" - "units: b(yte) k(Byte) K(iByte) m(Byte), M(iByte), g(Byte) G(iByte)\n" - "examples: -Z50m --max-size=1G"), - 'Z', "max-size", false, NULL); - m_programArgs.addString("minsize", - i18n("the filesize is greater or equal \n" - " is a number followed by an unit\n" - "units: b(yte) k(Byte) K(iByte) m(Byte), M(iByte), g(Byte) G(iByte)\n" - "examples: -z50m --min-size=1G"), - 'z', "min-size", false, NULL); -} - -/** - * Checks whether the given value is a time expression. - * - * Possible: - * Units: m(inutes) h(our) d(ays) - * - * @param value value to check - * @return the value converted into the absolute time - * @throws ReOptionExecption - */ -time_t ReDirOptions::checkDate(const char* value){ - ReByteBuffer theValue(value, -1); - time_t rc = 0; - if (theValue.count(".") == 2){ - // a date: - int year, month, day; - int hour = 0; - int minute = 0; - switch (sscanf(value, "%d.%d.%d/%d:%dc", &year, &month, &day, &hour, &minute)){ - case 3: - case 4: - case 5: - { - if (year < 1970) - throw ReOptionException(&m_programArgs, - i18n("date < 1970.01.01: "), value); - struct tm time; - memset(&time, 0, sizeof time); - time.tm_year = year - 1900; - time.tm_mon = month - 1; - time.tm_mday = day; - time.tm_hour = hour; - time.tm_min = minute; - rc = mktime(&time); - break; - } - default: - throw ReOptionException(&m_programArgs, - i18n("invalid date/date-time value. yyyy.mm.dd/hh:MM expected"), value); - } - } else { - // a time distance value: - char unit = 'm'; - int count = 0; - switch(sscanf(value, "%d%c", &count, &unit)){ - case 1: - case 2: - switch(unit){ - case 'm': - count *= 60; - break; - case 'h': - count *= 60*60; - break; - case 'd': - count *= 24*60*60; - break; - default: - throw ReOptionException(&m_programArgs, - i18n("invalid unit. expected: m(inutes) h(ours) d(ays)"), value); - } - rc = time(NULL) - count; - break; - default: - throw ReOptionException(&m_programArgs, - i18n("invalid relative time value expected. : m h d"), - value); - } - } - return rc; -} -/** - * Checks whether the given value is a time expression. - * - * Possible: - * Units: m(inutes) h(our) d(ays) - * - * @param value value to check - * @return the value (multiplied with the unit factor) - * @throws ReOptionExecption - */ -time_t ReDirOptions::checkSize(const char* value){ - int64_t rc = 0; - char unit = 'b'; - switch (sscanf(value, "%lld%c", (long long int*) &rc, &unit)){ - case 1: - case 2: - switch(unit){ - case 'b': - break; - case 'k': - rc *= 1000; - break; - case 'K': - rc *= 1024; - break; - case 'm': - rc *= 1000*1000; - break; - case 'M': - rc *= 1024*1024; - break; - case 'g': - rc *= 1000LL*1000*1000; - break; - case 'G': - rc *= 1024LL*1024*1024; - break; - default: - throw ReOptionException(&m_programArgs, - i18n("invalid . Expected: b k K m M g G"), - value); - } - break; - default: - throw ReOptionException(&m_programArgs, - i18n("invalid size value: : b k K m M g G"), - value); - } - return rc; -} -/** - * Checks whether the given value is a valid pattern list. - * - * The first character must be a separator (not '*', '.' and not a letter) - * - * @param value value to check - * @return the value (for chaining) - * @throws ReOptionExecption - */ -const char* ReDirOptions::checkPatternList(const char* value){ - if (isalnum(*value) || *value == '_' || *value == '*' - || *value == '.' || *value == '-') - throw ReOptionException(&m_programArgs, - i18n("invalid separator (first character): $1 use ';' instead"), - value); - if (strchr(value, OS_SEPARATOR_CHAR) != NULL) - throw ReOptionException(&m_programArgs, - i18n("slash not allowed in pattern list: $2"), value); - return value; -} -/** - * Checks whether the given value is a valid filetype list. - * - * @param value value to check - * @return the bitmask - * @throws ReOptionExecption - */ -ReDirStatus_t::Type_t ReDirOptions::checkType(const char* value){ - int rc = ReDirStatus_t::TF_UNDEF; - while (*value != '\0'){ - switch(*value){ - case 'b': - rc |= ReDirStatus_t::TF_BLOCK; - break; - case 'c': - rc |= ReDirStatus_t::TF_CHAR; - break; - case 'd': - rc |= ReDirStatus_t::TF_SUBDIR; - break; - case 'i': - rc |= ReDirStatus_t::TF_LINK_DIR; - break; - case 'l': - rc |= ReDirStatus_t::TF_LINK; - break; - case 'o': - rc |= ReDirStatus_t::TF_OTHER; - break; - case 'p': - rc |= ReDirStatus_t::TF_PIPE; - break; - case 's': - rc |= ReDirStatus_t::TF_SOCKET; - break; - case 'r': - rc |= ReDirStatus_t::TF_REGULAR; - break; - case 'S': - rc |= ReDirStatus_t::TC_SPECIAL; - break; - case 'N': - rc |= ReDirStatus_t::TC_NON_DIR; - break; - case ' ': - case ',': - break; - default: - throw ReOptionException(&m_programArgs, - i18n("invalid type: $1 Expected: b(lock) c(har) d(irectory)" - " (l)i(nkdir) l(ink) o(ther) p(ipe) s(ocket) r(egular)" - " S(pecial=bcspo) N(ondir=Slr)"), - value); - } - value++; - } - return (ReDirStatus_t::Type_t) rc; -} - -/** - * Prints a help message, the error message and exits. - * - * @param errorMessage the error message. - * @param message2 an additional message - */ - -void ReDirOptions::help(const char* errorMessage, const char* message2) const{ - ReByteBuffer msg; - if (errorMessage != 0) - msg.append(errorMessage, -1); - if (message2 != NULL) - msg.append(message2, -1); - m_programArgs.help(msg.str(), false, stdout); - exit(1); -} - -/** - * Checks the correctness of the standard filter options. - * - * @throws - */ -void ReDirOptions::checkStandardFilterOptions(){ - ReByteBuffer buffer; - checkDate(m_programArgs.getString("older", buffer)); - checkDate(m_programArgs.getString("younger", buffer)); - checkType(m_programArgs.getString("types", buffer)); - checkSize(m_programArgs.getString("maxsize", buffer)); - checkSize(m_programArgs.getString("minsize", buffer)); - checkPatternList(m_programArgs.getString("nodepattern", buffer)); - checkPatternList(m_programArgs.getString("pathpattern", buffer)); - if (m_programArgs.getString("verbose", buffer)[0] != '\0'){ - unsigned level = V_NORMAL; - if (ReStringUtils::lengthOfUnsigned(buffer.str(), -1, &level) - != buffer.length()) - help(i18n("verbose level is not a number (or '')"), buffer.str()); - else - m_verboseLevel = VerboseLevel(level); - } -} - -/** - * Frees the resources. - */ -void ReDirOptions::close(){ - if (m_output != stdout){ - fclose(m_output); - m_output = stdout; - } -} - -/** - * Initializes the compound usage message array. - * - * @param size the size of the array: size = (field1 + field2 + ...) * sizeof(const char*) - */ -void ReDirOptions::initCompoundUsage(size_t size){ - delete[] m_compoundUsage; - int count = size / sizeof m_compoundUsage[0]; - m_compoundUsage = new const char*[count]; - memset(m_compoundUsage, 0, size); - m_countCompoundUsage = count; -} - -/** - * Optimizes the path patterns. - * - * For all patterns of the list: - *
  • remove a trailing "\" and "\*"
  • - *
  • change "*\xy" to "*\xy" and "xy" (finds xy in the root directory)
  • - *
  • replaces "/" with the os specific path separator
  • - *
- * - * @param buffer the pattern list as string, e.g. ";*;-cache" - */ -void ReDirOptions::optimizePathPattern(ReByteBuffer& buffer){ - ReStringList list; - ReStringList rootList; - list.split(buffer.str() + 1, buffer.str()[0]); - buffer.replaceAll(OS_SEPARATOR, 1, "/", 1); - ReByteBuffer item; - for (int ix = 0; ix < (int) list.count(); ix++){ - item.set(list.strOf(ix), -1); - if (item.endsWith("/*")) - item.setLength(item.length() - 2); - if (item.endsWith("/")) - item.setLength(item.length() - 1); - bool notAnchored = item.startsWith("*/") || item.startsWith("-*/"); - item.replaceAll("/", 1, OS_SEPARATOR, 1); - list.replace(ix, item.str()); - if (notAnchored){ - item.remove(item.str()[0] == '-' ? 1 : 0, 2); - rootList.append(item.str(), 0); - } - } - if (rootList.count() > 0){ - list.append(rootList); - } - item.set(buffer.str(), 1); - buffer.set(item); - list.join(item.str(), buffer, true); -} -/** - * Sets the standard filter options given by the program arguments. - * - * @param filter OUT: the filter to set - */ -void ReDirOptions::setFilterFromProgramArgs(ReDirEntryFilter_t& filter){ - ReByteBuffer buffer; - if (m_programArgs.getString("younger", buffer)[0] != '\0') - filter.m_maxAge = checkDate(buffer.str()); - if (m_programArgs.getString("older", buffer)[0] != '\0') - filter.m_minAge = checkDate(buffer.str()); - if (m_programArgs.getString("maxsize", buffer)[0] != '\0') - filter.m_maxSize = checkSize(buffer.str()); - if (m_programArgs.getString("minsize", buffer)[0] != '\0') - filter.m_minSize = checkSize(buffer.str()); - if (m_programArgs.getString("type", buffer)[0] != '\0') - filter.m_types = checkType(buffer.str()); - filter.m_minDepth = m_programArgs.getInt("mindepth"); - filter.m_maxDepth = m_programArgs.getInt("maxdepth"); - if (m_programArgs.getString("nodepattern", buffer)[0] != '\0'){ - checkPatternList(buffer.str()); - m_nodePatterns.set(buffer.str()); - filter.m_nodePatterns = &m_nodePatterns; - } - if (m_programArgs.getString("pathpattern", buffer)[0] != '\0'){ - checkPatternList(buffer.str()); - optimizePathPattern(buffer); - m_pathPatterns.set(buffer.str()); - filter.m_pathPatterns = &m_pathPatterns; - } - if ( (m_interval = m_programArgs.getInt("trace")) != 0) - m_triggerCount = 10; - if (m_programArgs.getString("output", buffer)[0] != '\0'){ - if ( (m_output = fopen(buffer.str(), "w")) == NULL){ - help("cannot open output file", buffer.str()); - m_output = stdout; - } - } -} - -/** - * Constructor. - * - * @param usage a string vector with a message how to use the command - * @param example a string vector with some examples how to use the command - */ -ReTool::ReTool(const char* usage[], const char* example[]) : - ReDirOptions(usage, example), - ReDirStatisticData(), - m_traverser(NULL) -{ -} - -/** - * Destructor. - */ -ReTool::~ReTool(){ -} - +/* + * ReDirTools.cpp + * + * License: Public domain + * Do what you want. + * No warranties and disclaimer of any damages. + * The latest sources: https://github.com/republib + */ + +#include "base/rebase.hpp" +#include "os/reos.hpp" + +enum LOCATION_DIRTOOL { + LC_COPY_FILE_1 = LC_DIRTOOLS + 1, // 50101 + LC_COPY_FILE_2, // 50102 + LC_COPY_FILE_3, // 50103 + LC_COPY_FILE_4, // 50104 + LC_COPY_FILE_5, // 50105 + LC_COPY_FILE_6, // 50106 + LC_MAKE_DIR_1, // 50107 + LC_MAKE_DIR_2, // 50108 + LC_SET_PROPERTIES_1, // 50109 + LC_SET_PROPERTIES_2, // 50110 + LC_SET_PROPERTIES_3, // 50111 +}; +const char* ReDirTools::m_version = "2015.02.04"; + +static const char* s_helpSummary[] = { + "dirtool or dt ", + " Useful commands around directory trees.", + " Type 'dirtool help ' for more help.", + ":", + "batch produce output to handle the found files with a script", + "help shows info about the arguments/options", + "list shows the meta data of the selected files", + "statistic shows statistics about a direcctory tree", + "synchronize copies only modified or new files from", + " from a source directory tre to a target", + NULL +}; + +const char* s_batchUsage[] = { + ": batch", + " produces output usable for a batch file (script)", + " all found files can be processed with a given script", + " each line starts (usually) with a script name (see -c)", + " then it follows the full filename of the found file", + " use --arguments or --script to configure the output line", + NULL +}; +const char* s_batchExamples[] = { + "dirtool batch -cbackup.bat --basename-pattern=;*.txt;*.doc e:\\data", + "dirtool batch --type=r '--arguments=backup.sh !basename! !path!' usr etc", + NULL +}; + +const char* s_listUsage[] = { + ": list", + " lists the metadata (size, modification date ...) of the selected files", + NULL +}; +const char* s_listExamples[] = { + "dirtool list --min-size=10M e:\\data", + "dirtool list --type=f -y7d --size=10M -p;*.cpp;*.hpp;Makefile*;-*~ /home/data" , + NULL +}; + +static const char* s_statisticUsage[] = { + ":" + "st(atistic) [] []", + " shows a statistic about a directory tree", + " a directory path: relative or absolute", + " 0: only the summary of will be shown", + " 1: shows the summery of each subdir of and the total", + " n: shows the summery of each subdir until level and the total", + " default: 1", + ":", + NULL +}; +const char* s_statisticExamples[] = { + "dirtool st -q -t0 e:\\windows", + "dirtool statistic --quiet --kbyte --trace-interval=60 ../mail 2", + "dirtool stat -q --kbyte d:data 2", + NULL +}; + +const char* s_syncUsage[] = { + ":", + "sy(nchronize) [ ...] ", + " Synchronizes the content of a directory tree with another.", + " Newer or missing files will be copied", + " If a source name ends with the separator (/ in linux, \\ in win) the target", + " will be completed with the basename of the source.", + " Example:", + " 'sync home backup' copies the file home/x.txt to backup/home/x.txt", + " 'sync data/ backup/data2' copies the file home/x.txt to backup/data2/x.txt", + NULL +}; +const char* s_syncExamples[] = { + "dirtool sync --basename-pattern=;*.txt;*.doc e:\\data\\ d:\\backup\\data2", + "dirtool sync --type=r --max-size=1G usr etc /media/backup", + NULL +}; + +/** + * Constructor. + * + * @param usage a string vector with a message how to use the command + * @param example a string vector with some examples how to use the command + */ +ReDirOptions::ReDirOptions(const char* usage[], const char* examples[]) : + ReTraceUnit(INT_MAX, INT_MAX), + m_programArgs(usage, examples), + m_nodePatterns(), + m_pathPatterns(), + m_compoundUsage(NULL), + m_countCompoundUsage(0), + m_output(stdout), + m_verboseLevel(V_NORMAL) +{ + m_nodePatterns.setIgnoreCase(true); + m_pathPatterns.setIgnoreCase(true); +} +/** + * Destructor. + */ +ReDirOptions::~ReDirOptions(){ + close(); + delete[] m_compoundUsage; +} +/** + * Adds a usage component to the compound usage message list. + * @param usage a string vector containing a part of the usage message + */ +void ReDirOptions::addCompoundUsage(const char** usage){ + int start = 0; + while(m_compoundUsage[start] != NULL){ + if (++start >= m_countCompoundUsage) + assert(false); + } + for (int ix = 0; usage[ix] != NULL; ix++){ + if (start + ix > m_countCompoundUsage){ + assert(false); + break; + } + m_compoundUsage[start + ix] = usage[ix]; + } +} + +/** + * Adds the standard filter options. + */ +void ReDirOptions::addStandardFilterOptions(){ + // standard short options: D d O o P p T t v y Z z + m_programArgs.addInt("maxdepth", + i18n("the depth of the subdirectory is lower or equal \n" + "0: search is done only in the base directory"), + 'D', "max-depth", 512); + m_programArgs.addInt("mindepth", + i18n("the depth of the subdirectory is greater or equal \n" + "0: search is done in all subdirectories"), + 'd', "min-depth", 0); + m_programArgs.addString("output", + i18n("the name of the output file.\n" + "The output will written to this file instead of stdout"), + 'O', "output-file", false, NULL); + m_programArgs.addString("older", + i18n("the modification date is older than \n" + " is a date (e.g. 2015.02.17) or number followed by an unit\n" + "units: m(inutes) h(hours), d(days). Default: m(inutes)\n" + "examples: -o25 --older-than=30d -o24h -o2009.3.2/12:00 -o1999.01.01"), + 'o', "older-than", false, NULL); + m_programArgs.addString("pathpattern", + i18n("a list of patterns for the path (without basename)\n" + "the separator is the first character of the list\n" + "Each pattern can contain '*' as wildcard\n" + "If the first character is '-' the pattern is a 'not pattern':\n" + "A directory will be entered if at least one of the positive patterns\n" + "and none of the 'not patterns' matches\n" + "examples:\n" + "';music;pic*' enters music and xy/Music and PIC and pictures but not xy/pic and img\n" + "';*;-.git;.hg' ignores .git and xy/z/.git and .ht"), + 'P', "path-pattern", false, NULL); + m_programArgs.addString("nodepattern", + i18n("a list of patterns for the basename (name without path) separated by ';'\n" + "Each pattern can contain '*' as wildcard\n" + "If the first character is '-' the pattern is a 'not pattern':\n" + "A file will be found if at least one of the positive patterns and none\n" + "of the 'not patterns' matches\n" + "examples: '*.cpp;*.hpp;Make*' '*;-*.bak;-*~"), + 'p', "basename-pattern", false, NULL); + m_programArgs.addString("verbose", + i18n("verbose level: 0: no info, 1: summary only, 2: normal, 3: chatter mode, 4: debug"), + 'v', "verbose", false, "1"); + m_programArgs.addInt("trace", + i18n("all seconds the current path will be traced\n" + "0: no trace"), + 'T', "trace-interval", 0); + m_programArgs.addString("type", + i18n("the file type\n" + " is a list of values:\n" + ": b(lock) c(har) d(irectory) (l)i(nkdir) l(ink) o(ther)\n" + " p(ipe) s(ocket) r(egular)\n" + "-sets: S(pecial)=bcspo N(ondir)=Slr\n" + "examples: -td --type=dr -tNi"), + 't', "type", false, NULL); + m_programArgs.addString("younger", + i18n("the modification date is younger than \n" + " is a date (e.g. 2015.02.17) or number followed by an unit\n" + "units: m(inutes) h(hours), d(days). Default: m(inutes)"), + 'y', "younger-than", false, NULL); + m_programArgs.addString("maxsize", + i18n("the filesize is greater or equal \n" + " is a number followed by an unit\n" + "units: b(yte) k(Byte) K(iByte) m(Byte), M(iByte), g(Byte) G(iByte)\n" + "examples: -Z50m --max-size=1G"), + 'Z', "max-size", false, NULL); + m_programArgs.addString("minsize", + i18n("the filesize is greater or equal \n" + " is a number followed by an unit\n" + "units: b(yte) k(Byte) K(iByte) m(Byte), M(iByte), g(Byte) G(iByte)\n" + "examples: -z50m --min-size=1G"), + 'z', "min-size", false, NULL); +} + +/** + * Checks whether the given value is a time expression. + * + * Possible: + * Units: m(inutes) h(our) d(ays) + * + * @param value value to check + * @return the value converted into the absolute time + * @throws ReOptionExecption + */ +time_t ReDirOptions::checkDate(const char* value){ + ReByteBuffer theValue(value, -1); + time_t rc = 0; + if (theValue.count(".") == 2){ + // a date: + int year, month, day; + int hour = 0; + int minute = 0; + switch (sscanf(value, "%d.%d.%d/%d:%dc", &year, &month, &day, &hour, &minute)){ + case 3: + case 4: + case 5: + { + if (year < 1970) + throw ReOptionException(&m_programArgs, + i18n("date < 1970.01.01: "), value); + struct tm time; + memset(&time, 0, sizeof time); + time.tm_year = year - 1900; + time.tm_mon = month - 1; + time.tm_mday = day; + time.tm_hour = hour; + time.tm_min = minute; + rc = mktime(&time); + break; + } + default: + throw ReOptionException(&m_programArgs, + i18n("invalid date/date-time value. yyyy.mm.dd/hh:MM expected"), value); + } + } else { + // a time distance value: + char unit = 'm'; + int count = 0; + switch(sscanf(value, "%d%c", &count, &unit)){ + case 1: + case 2: + switch(unit){ + case 'm': + count *= 60; + break; + case 'h': + count *= 60*60; + break; + case 'd': + count *= 24*60*60; + break; + default: + throw ReOptionException(&m_programArgs, + i18n("invalid unit. expected: m(inutes) h(ours) d(ays)"), value); + } + rc = time(NULL) - count; + break; + default: + throw ReOptionException(&m_programArgs, + i18n("invalid relative time value expected. : m h d"), + value); + } + } + return rc; +} +/** + * Checks whether the given value is a time expression. + * + * Possible: + * Units: m(inutes) h(our) d(ays) + * + * @param value value to check + * @return the value (multiplied with the unit factor) + * @throws ReOptionExecption + */ +time_t ReDirOptions::checkSize(const char* value){ + int64_t rc = 0; + char unit = 'b'; + switch (sscanf(value, "%lld%c", (long long int*) &rc, &unit)){ + case 1: + case 2: + switch(unit){ + case 'b': + break; + case 'k': + rc *= 1000; + break; + case 'K': + rc *= 1024; + break; + case 'm': + rc *= 1000*1000; + break; + case 'M': + rc *= 1024*1024; + break; + case 'g': + rc *= 1000LL*1000*1000; + break; + case 'G': + rc *= 1024LL*1024*1024; + break; + default: + throw ReOptionException(&m_programArgs, + i18n("invalid . Expected: b k K m M g G"), + value); + } + break; + default: + throw ReOptionException(&m_programArgs, + i18n("invalid size value: : b k K m M g G"), + value); + } + return rc; +} +/** + * Checks whether the given value is a valid pattern list. + * + * The first character must be a separator (not '*', '.' and not a letter) + * + * @param value value to check + * @return the value (for chaining) + * @throws ReOptionExecption + */ +const char* ReDirOptions::checkPatternList(const char* value){ + if (isalnum(*value) || *value == '_' || *value == '*' + || *value == '.' || *value == '-') + throw ReOptionException(&m_programArgs, + i18n("invalid separator (first character): $1 use ';' instead"), + value); + if (strchr(value, OS_SEPARATOR_CHAR) != NULL) + throw ReOptionException(&m_programArgs, + i18n("slash not allowed in pattern list: $2"), value); + return value; +} +/** + * Checks whether the given value is a valid filetype list. + * + * @param value value to check + * @return the bitmask + * @throws ReOptionExecption + */ +ReDirStatus_t::Type_t ReDirOptions::checkType(const char* value){ + int rc = ReDirStatus_t::TF_UNDEF; + while (*value != '\0'){ + switch(*value){ + case 'b': + rc |= ReDirStatus_t::TF_BLOCK; + break; + case 'c': + rc |= ReDirStatus_t::TF_CHAR; + break; + case 'd': + rc |= ReDirStatus_t::TF_SUBDIR; + break; + case 'i': + rc |= ReDirStatus_t::TF_LINK_DIR; + break; + case 'l': + rc |= ReDirStatus_t::TF_LINK; + break; + case 'o': + rc |= ReDirStatus_t::TF_OTHER; + break; + case 'p': + rc |= ReDirStatus_t::TF_PIPE; + break; + case 's': + rc |= ReDirStatus_t::TF_SOCKET; + break; + case 'r': + rc |= ReDirStatus_t::TF_REGULAR; + break; + case 'S': + rc |= ReDirStatus_t::TC_SPECIAL; + break; + case 'N': + rc |= ReDirStatus_t::TC_NON_DIR; + break; + case ' ': + case ',': + break; + default: + throw ReOptionException(&m_programArgs, + i18n("invalid type: $1 Expected: b(lock) c(har) d(irectory)" + " (l)i(nkdir) l(ink) o(ther) p(ipe) s(ocket) r(egular)" + " S(pecial=bcspo) N(ondir=Slr)"), + value); + } + value++; + } + return (ReDirStatus_t::Type_t) rc; +} + +/** + * Prints a help message, the error message and exits. + * + * @param errorMessage the error message. + * @param message2 an additional message + */ + +void ReDirOptions::help(const char* errorMessage, const char* message2) const{ + ReByteBuffer msg; + if (errorMessage != 0) + msg.append(errorMessage, -1); + if (message2 != NULL) + msg.append(message2, -1); + m_programArgs.help(msg.str(), false, stdout); + exit(1); +} + +/** + * Checks the correctness of the standard filter options. + * + * @throws + */ +void ReDirOptions::checkStandardFilterOptions(){ + ReByteBuffer buffer; + checkDate(m_programArgs.getString("older", buffer)); + checkDate(m_programArgs.getString("younger", buffer)); + checkType(m_programArgs.getString("types", buffer)); + checkSize(m_programArgs.getString("maxsize", buffer)); + checkSize(m_programArgs.getString("minsize", buffer)); + checkPatternList(m_programArgs.getString("nodepattern", buffer)); + checkPatternList(m_programArgs.getString("pathpattern", buffer)); + if (m_programArgs.getString("verbose", buffer)[0] != '\0'){ + unsigned level = V_NORMAL; + if (ReStringUtils::lengthOfUnsigned(buffer.str(), -1, &level) + != buffer.length()) + help(i18n("verbose level is not a number (or '')"), buffer.str()); + else + m_verboseLevel = VerboseLevel(level); + } +} + +/** + * Frees the resources. + */ +void ReDirOptions::close(){ + if (m_output != stdout){ + fclose(m_output); + m_output = stdout; + } +} + +/** + * Initializes the compound usage message array. + * + * @param size the size of the array: size = (field1 + field2 + ...) * sizeof(const char*) + */ +void ReDirOptions::initCompoundUsage(size_t size){ + delete[] m_compoundUsage; + int count = size / sizeof m_compoundUsage[0]; + m_compoundUsage = new const char*[count]; + memset(m_compoundUsage, 0, size); + m_countCompoundUsage = count; +} + +/** + * Optimizes the path patterns. + * + * For all patterns of the list: + *
  • remove a trailing "\" and "\*"
  • + *
  • change "*\xy" to "*\xy" and "xy" (finds xy in the root directory)
  • + *
  • replaces "/" with the os specific path separator
  • + *
+ * + * @param buffer the pattern list as string, e.g. ";*;-cache" + */ +void ReDirOptions::optimizePathPattern(ReByteBuffer& buffer){ + ReStringList list; + ReStringList rootList; + list.split(buffer.str() + 1, buffer.str()[0]); + buffer.replaceAll(OS_SEPARATOR, 1, "/", 1); + ReByteBuffer item; + for (int ix = 0; ix < (int) list.count(); ix++){ + item.set(list.strOf(ix), -1); + if (item.endsWith("/*")) + item.setLength(item.length() - 2); + if (item.endsWith("/")) + item.setLength(item.length() - 1); + bool notAnchored = item.startsWith("*/") || item.startsWith("-*/"); + item.replaceAll("/", 1, OS_SEPARATOR, 1); + list.replace(ix, item.str()); + if (notAnchored){ + item.remove(item.str()[0] == '-' ? 1 : 0, 2); + rootList.append(item.str(), 0); + } + } + if (rootList.count() > 0){ + list.append(rootList); + } + item.set(buffer.str(), 1); + buffer.set(item); + list.join(item.str(), buffer, true); +} +/** + * Sets the standard filter options given by the program arguments. + * + * @param filter OUT: the filter to set + */ +void ReDirOptions::setFilterFromProgramArgs(ReDirEntryFilter_t& filter){ + ReByteBuffer buffer; + if (m_programArgs.getString("younger", buffer)[0] != '\0') + filter.m_maxAge = checkDate(buffer.str()); + if (m_programArgs.getString("older", buffer)[0] != '\0') + filter.m_minAge = checkDate(buffer.str()); + if (m_programArgs.getString("maxsize", buffer)[0] != '\0') + filter.m_maxSize = checkSize(buffer.str()); + if (m_programArgs.getString("minsize", buffer)[0] != '\0') + filter.m_minSize = checkSize(buffer.str()); + if (m_programArgs.getString("type", buffer)[0] != '\0') + filter.m_types = checkType(buffer.str()); + filter.m_minDepth = m_programArgs.getInt("mindepth"); + filter.m_maxDepth = m_programArgs.getInt("maxdepth"); + if (m_programArgs.getString("nodepattern", buffer)[0] != '\0'){ + checkPatternList(buffer.str()); + m_nodePatterns.set(buffer.str()); + filter.m_nodePatterns = &m_nodePatterns; + } + if (m_programArgs.getString("pathpattern", buffer)[0] != '\0'){ + checkPatternList(buffer.str()); + optimizePathPattern(buffer); + m_pathPatterns.set(buffer.str()); + filter.m_pathPatterns = &m_pathPatterns; + } + if ( (m_interval = m_programArgs.getInt("trace")) != 0) + m_triggerCount = 10; + if (m_programArgs.getString("output", buffer)[0] != '\0'){ + if ( (m_output = fopen(buffer.str(), "w")) == NULL){ + help("cannot open output file", buffer.str()); + m_output = stdout; + } + } +} + +/** + * Constructor. + * + * @param usage a string vector with a message how to use the command + * @param example a string vector with some examples how to use the command + */ +ReTool::ReTool(const char* usage[], const char* example[]) : + ReDirOptions(usage, example), + ReDirStatisticData(), + m_traverser(NULL) +{ +} + +/** + * Destructor. + */ +ReTool::~ReTool(){ +} + /** * Prints a message. * @@ -592,1007 +592,1009 @@ ReTool::~ReTool(){ * @param currentFile message for the trace * @return true (for chaining) */ -bool ReTool::trace(const char* currentFile){ - ReByteBuffer buffer(" "); - int duration = int(time(NULL) - m_startTime); - buffer.appendInt(duration / 60).appendInt(duration % 60, ":%02d: "); - buffer.appendInt(m_files).append("/", 1).appendInt(m_traverser->directories()).append(" dir(s)"); - buffer.appendInt(m_files).append("/", 1).appendInt(m_traverser->files()).append(" dir(s)"); - buffer.append(currentFile); - fputs(buffer.str(), stdout); - return true; -} -/** - * Constructor. - */ -ReDirStatisticData::ReDirStatisticData() : - m_sizes(0), - m_files(0), - m_dirs(0), - m_path() -{ -} -/** - * Copy constructor. - * - * @param source the source to copy - */ -ReDirStatisticData::ReDirStatisticData(const ReDirStatisticData& source) : - m_sizes(source.m_sizes), - m_files(source.m_files), - m_dirs(source.m_dirs), - m_path(source.m_path) -{ -} -/** - * Assignment operator. - * - * @param source the source to copy - * @return the instance itself - */ -ReDirStatisticData& ReDirStatisticData::operator =(const ReDirStatisticData& source){ - m_sizes = source.m_sizes; - m_files = source.m_files; - m_dirs = source.m_dirs; - m_path = source.m_path; - return *this; -} - -/** - * Constructor. - */ -ReDirStatistic::ReDirStatistic(int deltaList, int deltaBuffer) : - ReDirOptions(s_statisticUsage, s_statisticExamples), - m_list(deltaList, deltaBuffer), - m_traceInterval(0), - m_lastTrace(0) -{ - // standard short options: D d O o P p T t v y Z z - m_programArgs.addBool("kbyte", - i18n("output format is ' ' (like unix 'du' command)"), - 'k', "kbyte", false); - addStandardFilterOptions(); -} -/** - * Destructor. - */ -ReDirStatistic::~ReDirStatistic(){ -} - -/** - * Adds the data from another instance. - * - * @param source the other instance - * @return the instance itself - */ -ReDirStatisticData& ReDirStatisticData::add(const ReDirStatisticData& source){ - m_sizes += source.m_sizes; - m_files += source.m_files; - m_dirs += source.m_dirs; - return *this; -} -/** - * Initializes the data of the instance. - */ -void ReDirStatisticData::clear(){ - m_sizes = 0; - m_files = 0; - m_dirs = 0; - m_path.setLength(0); -} - -/** - * Calculates the statistic of a directory tree. - * - * - */ -const ReStringList& ReDirStatistic::calculate(const char* base, int level, - void (*formatter)(const ReDirStatisticData& data, ReDirStatistic& parent, - ReByteBuffer& line)){ - ReDirEntryFilter_t filter; - ReTraverser traverser(base); - setFilterFromProgramArgs(filter); - traverser.setPropertiesFromFilter(&filter); - if (level > 1024) - level = 1024; - else if (level < 0) - level = 0; - m_list.clear(); - ReDirStatisticData** dataStack = new ReDirStatisticData*[level + 1]; - memset(dataStack, 0, sizeof dataStack[0] * (level + 1)); - dataStack[0] = new ReDirStatisticData(); - int topOfStack = 0; - ReDirStatus_t* entry; - int currentDepth = -1; - ReDirStatisticData* current = dataStack[0]; - current->m_path.set(base, -1); - ReByteBuffer line; - bool useFilter = filter.m_minSize > 0 || filter.m_maxSize != -1 - || filter.m_minAge != 0 || filter.m_maxAge != 0 - || m_nodePatterns.count() > 0; - while( (entry = traverser.rawNextFile(currentDepth))){ - if (currentDepth <= level && ! entry->m_path.equals(current->m_path)){ - if (currentDepth <= topOfStack){ - // close the entries of the stack above the new top level: - while(topOfStack >= currentDepth){ - // Add the data to the parent: - if (topOfStack > 0) - dataStack[topOfStack - 1]->add(*dataStack[topOfStack]); - // Append it to the result: - (*formatter)(*dataStack[topOfStack], *this, line); - m_list.append(line); - topOfStack--; - } - // We reuse the top of stack: - topOfStack++; - current = dataStack[topOfStack]; - // exchange the top of stack with the new found directory: - current->clear(); - current->m_path.set(entry->m_path.str(), entry->m_path.length());; - } else { - // set up a new stack entry: - if (currentDepth != topOfStack + 1) - assert(currentDepth == topOfStack + 1); - - topOfStack++; - if (dataStack[topOfStack] == NULL) - dataStack[topOfStack] = new ReDirStatisticData(); - else - dataStack[topOfStack]->clear(); - current = dataStack[topOfStack]; - current->m_path.set(entry->m_path.str(), entry->m_path.length()); - } - } - if (entry->isDirectory()){ - current->m_dirs++; - } else if (! useFilter || filter.match(*entry)){ - current->m_sizes += entry->fileSize(); - current->m_files++; - } - } - // close all dirs with parents: - while(topOfStack > 0){ - // Add the data to the parent: - dataStack[topOfStack - 1]->add(*dataStack[topOfStack]); - // Append it to the result: - (*formatter)(*dataStack[topOfStack], *this, line); - m_list.append(line); - topOfStack--; - } - // ... and the overall summery: - (*formatter)(*dataStack[0], *this, line); - m_list.append(line); - // free the resources: - for (int ix = 0; ix <= level; ix++) - delete dataStack[ix]; - delete[] dataStack; - return m_list; -} - -/** - * Build the statistic and print the results. - * - * @param arc count of arguments in argv - * @param argv the program arguments. - */ -void ReDirStatistic::run(int argc, const char* argv[]){ - time_t start = time(NULL); - try { - m_programArgs.init(argc, argv); - if (m_programArgs.getArgCount() < 1) - m_programArgs.help("statistic: missing path", false, stdout); - int depth = 1; - if (m_programArgs.getArgCount() > 1) - depth = atol(m_programArgs.getArg(1)); - ReDirStatistic statistic; - statistic.setTraceInterval(m_programArgs.getInt("trace")); - void (*proc) (const ReDirStatisticData& data, - ReDirStatistic& parent, ReByteBuffer& line) = &formatWithSizeFilesAndDirs; - if (m_programArgs.getBool("kbyte")) - proc = &formatLikeDu; - const ReStringList& list = statistic.calculate(m_programArgs.getArg(0), depth, proc); - ReByteBuffer buffer; - for (size_t ix = 0; ix < list.count(); ix++){ - buffer.set(list.strOf(ix), list.strLengthOf(ix)); - fprintf(m_output, "%s\n", buffer.str()); - } - if (m_verboseLevel >= V_SUMMARY){ - int duration = int(time(NULL) - start); - fprintf(m_output, "Duration: "); - if (duration >= 3600) - fprintf(m_output, "%d:", duration / 3600); - fprintf(m_output, "%02d:%02d\n", duration % 3600 / 60, duration % 60); - } - } catch (ReOptionException& exc) { - m_programArgs.help(exc.getMessage(), false, stdout); - } -} -/** - * Formats a line like the du (disk usage) command. - * - * This is a possible parameter of ReDirStatistic::calculate(). - * - * @param data statistic data, including path name - * @param parent the caller (ReDirStatistic). This allows to deliver - * a context to this formatting routine (through derivation of - * ReDirStatistic) - * @param line OUT: the formatted line, the conclusion of the statistic data - */ -void formatLikeDu(const ReDirStatisticData& data, - ReDirStatistic& parent, ReByteBuffer& line){ - line.setLength(0); - // Round up to the next KiByte: - line.appendInt(int((data.m_sizes + 1023) / 1024)).append("\t").append(data.m_path); -} - -/** - * Formats a line in a standard way: MBytes, file count and directory count. - * - * This is a possible parameter of ReDirStatistic::calculate(). - * - * @param data statistic data, including path name - * @param parent the caller (ReDirStatistic). This allows to deliver - * a context to this formatting routine (through derivation of - * ReDirStatistic) - * @param line OUT: the formatted line, the conclusion of the statistic data - */ -void formatWithSizeFilesAndDirs(const ReDirStatisticData& data, - ReDirStatistic& parent, ReByteBuffer& line){ - line.setLength(0); - // Round up to the next KiByte: - char buffer[256]; - _snprintf(buffer, sizeof buffer, "%14.6f MB %7d %7d\t", - data.m_sizes / 1E6, data.m_files, data.m_dirs); - line.append(buffer, -1).append(data.m_path); -} -/** - * Prints a vector of lines. - * - * @param lines a vector of lines without newline ('\n') - */ -static void printField(const char** lines){ - for (int ix = 0; lines[ix] != NULL; ix++){ - printf("%s\n", lines[ix]); - } -} - -/** - * Prints an message how to use the statistic module and exits. - */ -void ReDirTools::statisticUsage(){ - ReDirStatistic statistic; - statistic.programArgs().help(NULL, false, stdout); -} - -/** - * Prints an message how to use the program and exits. - * - * @param msg an error message - * @param msg2 an addition to the error message or NULL - */ -void ReDirTools::usage(const char* msg, const char* msg2){ - printf ("Version: %s\n", m_version); - printf ("usage: dirtool \n"); - statisticUsage(); - if (msg != NULL) - printf ("+++ %s%s\n", msg, msg2 == NULL ? "" : msg2); - exit(1); -} - -/** - * Constructor. - */ -ReDirList::ReDirList() : - ReTool(s_listUsage, s_listExamples) -{ - m_programArgs.addBool("short", i18n("output is only path and basename"), - '1', "--short", false); - addStandardFilterOptions(); -} - -/** - * Gets the arguments for the "list" command and execute this. - * - * @param argc the number of arguments - * @param argav the argument vector - */ -void ReDirList::list(int argc, const char* argv[]){ - ReDirEntryFilter_t filter; - try { - time_t start = time(NULL); - m_programArgs.init(argc, argv); - bool shortOutput = m_programArgs.getBool("short"); - setFilterFromProgramArgs(filter); - bool noPath = m_programArgs.getArgCount() == 0; - ReByteBuffer bufferRights; - ReByteBuffer bufferTime; - int64_t sumSizes = 0; - int files = 0; - int dirs = 0; - - for (int ix = 0; noPath || ix < m_programArgs.getArgCount(); ix++){ - ReTraverser traverser(noPath ? "." : m_programArgs.getArg(ix)); - noPath = false; - traverser.setPropertiesFromFilter(&filter); - int level; - ReDirStatus_t* entry; - while( (entry = traverser.nextFile(level, &filter)) != NULL){ - if (entry->isDirectory()) - dirs++; - else{ - files++; - sumSizes += entry->fileSize(); - } - if (! printOneFile(entry)){ - if (shortOutput) - fprintf(m_output, "%s%s\n", entry->m_path.str(), entry->node()); - else - fprintf(m_output, "%s %12.6f %s %02x %s%s\n", - entry->rightsAsString(bufferRights), - entry->fileSize() / 1E6, - entry->filetimeAsString(bufferTime), - entry->type(), - entry->m_path.str(), entry->node()); - } - } - } - if (m_verboseLevel >= V_SUMMARY){ - int duration = int(time(NULL) - start); - fprintf(m_output, "+++ %d dirs and %d file(s) with %.6f MByte in %02d:%02d sec\n", - dirs, files, sumSizes / 1E6, duration / 60, duration % 60); - } - } catch(ReOptionException& exc){ - help(exc.getMessage()); - } -} - -/** - * Constructor. - */ -ReDirBatch::ReDirBatch() : - ReTool(s_batchUsage, s_batchExamples) -{ - // standard short options: D d O o P p T t v y Z z - m_programArgs.addString("first", - i18n("defines the first line of the output"), - '1', "first-line", true, -#if defined __linux__ - "#! /bin/sh" -#elif defined __WIN32__ - "rem this batch is created by dirtool" -#endif - ); - m_programArgs.addString("arguments", - i18n("template for the output line.\n" - "Possible placeholders: (e.g. e:\\data\\sample.txt)\n" - " !full!: e:\\data\\sample.txt\n" - " !path!: e:\\data\\\n" - " !basename!: sample.txt\n" - " !name!: sample\n" - " !ext!: .txt\n" - "example: --arguments='echo !basename! in !path! found'"), - 'a', "arguments", false, NULL); - m_programArgs.addString("script", - i18n("name of the script (starts each output line)"), - 'c', "script", false, NULL); -#if defined __WIN32__ - m_programArgs.addBool("isexe", - i18n("supresses the starting 'call' of each output line" - "neccessary if a *.exe will be called (instead of a *.bat)"), - 'x', "is-exe", false); -#endif - addStandardFilterOptions(); -} - -static void replaceMakros(const char* arguments, ReDirStatus_t* entry, const char* delim, ReByteBuffer& line){ - line.set(arguments, -1); - // we prepare the removal of unwanted delimiters in constructed placeholders: - // example: !path!!name!: without correction: "e:\\data\\""xxx" - // We want: "e:\\data\\xxx" - line.replaceAll("!!", 2, "!\x01!", 3); - ReByteBuffer replacement; - if (strstr(arguments, "!full!") != NULL){ - replacement.set(delim, -1).append(entry->m_path); - replacement.append(entry->node(), -1).append(delim, -1); - line.replaceAll("!full!", 6, replacement.str(), replacement.length()); - } - if (strstr(arguments, "!path!") != NULL){ - replacement.set(delim, -1).append(entry->m_path).append(delim, -1); - line.replaceAll("!path!", 6, replacement.str(), replacement.length()); - } - if (strstr(arguments, "!basename!") != NULL){ - replacement.set(delim, -1).append(entry->node(), -1).append(delim, -1); - line.replaceAll("!basename!", 10, replacement.str(), replacement.length()); - } - if (strstr(arguments, "!name!") != NULL){ - replacement.set(delim, -1).append(entry->node(), -1); - int ix = replacement.rindexOf(".", 1); - if (ix > 1) - replacement.setLength(ix); - replacement.append(delim, -1); - line.replaceAll("!name!", 6, replacement.str(), replacement.length()); - } - if (strstr(arguments, "!ext!") != NULL){ - replacement.set(delim, -1).append(entry->node(), -1); - int ix = replacement.rindexOf(".", 1); - if (ix > 1) - replacement.remove(1, ix - 1); - else - replacement.setLength(1); - replacement.append(delim, -1); - line.replaceAll("!ext!", 5, replacement.str(), replacement.length()); - } - // We remove the unwanted delimiters (see above): - ReByteBuffer buffer; - buffer.set(delim, -1).append("\x01", 1).append(delim, -1); - line.replaceAll(buffer.str(), buffer.length(), "", 0); -} -/** - * Gets the arguments for the "list" command and execute this. - * - * @param argc the number of arguments - * @param argav the argument vector - */ -void ReDirBatch::createBatch(int argc, const char* argv[]){ - ReDirEntryFilter_t filter; - try { - time_t start = time(NULL); - m_programArgs.init(argc, argv); - ReByteBuffer buffer; - ReByteBuffer arguments(m_programArgs.getString("arguments", buffer), -1); - ReByteBuffer script(m_programArgs.getString("script", buffer), -1); - if (arguments.length() + script.length() == 0) - help(i18n("one of the option must be set: -a (--arguments) or -c (--script)")); -#if defined __WIN32__ - bool isExe = m_programArgs.getBool("isexe"); -#endif - setFilterFromProgramArgs(filter); - if (m_programArgs.getArgCount() == 0) - help(i18n("no arguments given (missing path)")); - if (m_programArgs.getString("first", buffer)[0] != '\0') - printf("%s\n", buffer.str()); - int64_t sumSizes = 0; - int files = 0; - int dirs = 0; -#if defined __linux__ - const char* delim = "'"; -#elif defined __WIN32__ - const char* delim = "\""; -#endif - for (int ix = 0; ix < m_programArgs.getArgCount(); ix++){ - ReTraverser traverser(m_programArgs.getArg(ix)); - traverser.setPropertiesFromFilter(&filter); - int level; - ReDirStatus_t* entry; - ReByteBuffer line; - while( (entry = traverser.nextFile(level, &filter)) != NULL){ - if (entry->isDirectory()) - dirs++; - else{ - files++; - sumSizes += entry->fileSize(); - } - if (script.length() > 0){ - line.setLength(0); -#if defined __WIN32__ - if (! isExe) - line.append("call "); -#endif - line.append(script).append(" ").append(delim, -1); - line.append(entry->m_path).append(entry->node(), -1); - line.append(delim, -1); - } else { - replaceMakros(arguments.str(), entry, delim, line); - } - fprintf(m_output, "%s\n", line.str()); - } - } - if (m_verboseLevel >= V_SUMMARY){ - int duration = int(time(NULL) - start); -#if defined __linux__ - const char* prefix = "#"; -#elif defined __WIN32__ - const char* prefix = "rem"; -#endif - fprintf(m_output, "%s %d dir(s) and %d file(s) with %.6f MByte in %02d:%02d sec\n", - prefix, dirs, files, sumSizes / 1E6, duration / 60, duration % 60); - } - } catch(ReOptionException& exc){ - help(exc.getMessage()); - } -} - -/** - * creates a subdirectory (and the parent directories if neccessary. - * - * @param path the name of the subdir to create - */ -void ReDirSync::makeDirWithParents(ReByteBuffer& path, int minWidth, - ReTraverser& traverser){ - struct stat info; - bool endsWithSlash = path.str()[path.length() - 1] == OS_SEPARATOR_CHAR; - if (endsWithSlash) - path.setLength(path.length() - 1); - if (stat(path.str(), &info) != 0){ - ReFileProperties_t* props = NULL; -#if defined __linux__ - ReDirStatus_t* entry = traverser.topOfStack(); - props = entry == NULL ? NULL : &entry->m_status; -#endif - makeDirectory(path.str(), minWidth, props, ReLogger::globalLogger()); - } - if (endsWithSlash) - path.append(OS_SEPARATOR, 1); -} -/** - * Constructor. - */ -ReDirSync::ReDirSync() : - ReTool(s_syncUsage, s_syncExamples), - m_buffer() -{ - // standard short options: D d O o P p T t v y Z z - m_buffer.ensureSize(4u*1024u*1024u); - m_programArgs.addBool("add", - i18n("copies only files which does not exist on the target"), - 'a', "add", false); - m_programArgs.addBool("dry", - i18n("does nothing, but says what should be done"), - 'Y', "dry", false); - m_programArgs.addInt("timediff", - i18n("filetime difference is considered to be equal\n" - "if the difference is less than this value (in seconds)"), - 'I', "time-delta", 2); - m_programArgs.addBool("ignoredate", - i18n("the modification is recognized only by the different size (not time)"), - 'i', "ignore-time", false); - m_programArgs.addBool("mustexist", - i18n("files which don't exist on the target will not be copied"), - 'm', "must-exist", false); - addStandardFilterOptions(); -} - -/** - * Copies a file. - * - * @param entry the source file info - * @param target the name of the target file - */ -void ReDirSync::copyFile(ReDirStatus_t* entry, const char* target){ - ReFileProperties_t* props; -#ifdef __linux__ - props = &entry->m_status; -#else - ReFileProperties_t properties; - properties.m_modified = *entry->modified(); - properties.m_accessed = *entry->accessed(); - properties.m_size = entry->fileSize(); - props = &properties; -#endif - copyFile(entry->fullName(), props, target, m_buffer, - ReLogger::globalLogger()); -} - -/** - * Copies a file. - * - * @param source the source file name - * @param properties NULL or the properties of the source file - * @param target the name of the target file - * @param buffer OUT: the reading uses this buffer
- * Set the capacity to make it more efficient - * @param logger NULL or the logger for error messages - * @return truesuccess
- * falseerror occurred - */ -bool ReDirSync::copyFile(const char* source, ReFileProperties_t* properties, - const char* target, ReByteBuffer& buffer, ReLogger* logger){ - bool rc = false; -#ifdef __linux__ - struct stat info; - if (properties == NULL){ - if (stat(source, &info) == 0) - properties = &info; - else { - if (logger != NULL) - logger->sayF(LOG_ERROR | CAT_FILE, LC_COPY_FILE_1, - i18n("could not find: $ (errno: $2)")).arg(source) - .arg(errno).end(); - } - } - FILE* fpSource = fopen(source, "rb"); - if (fpSource == NULL){ - if (logger != NULL) - logger->sayF(LOG_ERROR | CAT_FILE, LC_COPY_FILE_2, - i18n("cannot open $1 (errno: $2)")) - .arg(source).arg(errno).end(); - } else { - FileSize_t size = properties == NULL ? 0x7fffffff : properties->st_size; - FILE* fpTarget = fopen(target, "w"); - if (fpTarget == NULL){ - if (logger != NULL) - logger->sayF(LOG_ERROR | CAT_FILE, LC_COPY_FILE_3, - i18n("cannot open $1 (errno: $2)")) - .arg(target).arg(errno).end(); - } else{ - while(size > 0){ - size_t blockSize = buffer.capacity(); - if ((int) blockSize > size) - blockSize = size; - if (fread(buffer.buffer(), blockSize, 1, fpSource) != 1){ - if (logger != NULL) - logger->sayF(LOG_ERROR | CAT_FILE, LC_COPY_FILE_5, - i18n("cannot read $1 (errno: $2)")) - .arg(source).arg(errno).end(); - break; - } - size_t written; - if ((written = fwrite(buffer.buffer(), 1, blockSize, - fpTarget)) != blockSize){ - if (logger != NULL) - logger->sayF(LOG_ERROR | CAT_FILE, LC_COPY_FILE_6, - i18n("cannot write $1 [$2] (errno: $3)")) - .arg(target).arg(written).arg(errno).end(); - break; - } - size -= blockSize; - } - rc = size == 0ll; - fclose(fpTarget); - if (properties != NULL) - setProperties(target, properties, logger); - } - fclose(fpSource); - } -#elif defined __WIN32__ - BOOL cancel = false; - rc = CopyFileExA(source, target, NULL, NULL, &cancel, COPY_FILE_NO_BUFFERING) != 0; - int errNo = 0; - if (! rc) - errNo = GetLastError(); -#endif - return rc; -} -/** - * Sets the file properties. - * - * @param fullName the name of the file - * @param properties the properties like times and rights - * @param logger NULL or the logger for error messages - * @return true: success
- * false: error occurred - */ -bool ReDirSync::setProperties(const char* fullName, - ReFileProperties_t* properties, ReLogger* logger){ - bool rc = true; -#if defined __linux__ - struct utimbuf times; - times.actime = properties->st_atime; - times.modtime = properties->st_mtime; - if (utime(fullName, ×) != 0){ - if (logger != NULL) - logger->sayF(LOG_ERROR | CAT_FILE, LC_SET_PROPERTIES_1, - i18n("cannot change file times: $1 (errno: $2)")) - .arg(fullName).arg(errno).end(); - rc = false; - } - int rights = properties->st_mode & (S_IRWXO | S_IRWXG | S_IRWXU); - if (chmod(fullName, rights) != 0) { - if (logger != NULL) - logger->sayF(LOG_ERROR | CAT_FILE, LC_SET_PROPERTIES_2, - i18n("cannot change file modes: $1 (errno: $2)")) - .arg(fullName).arg(errno).end(); - rc = false; - } - if (chown(fullName, properties->st_uid, properties->st_gid) != 0){ - if (logger != NULL) - logger->sayF(LOG_ERROR | CAT_FILE, LC_SET_PROPERTIES_3, - i18n("cannot change file owner: $1 (errno: $2)")) - .arg(fullName).arg(errno).end(); - rc = false; - } -#endif - return rc; -} - -/** - * Creates a directory and its parents (if neccessary). - * - * @param directory the full name of the directory - * @param properties NULL or the properties of the new directory - * @param logger NULL or the logger for error messages - * @return truesuccess
- * falseerror occurred - */ -bool ReDirSync::makeDirectory(const char* directory, int minLength, - ReFileProperties_t* properties, ReLogger* logger){ - bool rc = true; - ReByteBuffer path(directory); - int start = 0; -#if defined __WIN32__ - start = path.indexOf(':'); -#endif - int ixSlash = start < 0 ? 0 : start; - struct stat info; - // for all parents and the full path itself: - while(ixSlash >= 0){ - ixSlash = path.indexOf(OS_SEPARATOR_CHAR, ixSlash + 1); - if (ixSlash >= (int) path.length() - 1) - break; - // is the slash in front of the first node, e.g. 'e:\'? - if (ixSlash == start + 1) - // not a real node: take the next node - ixSlash = path.indexOf(OS_SEPARATOR_CHAR, ixSlash + 1); - if (ixSlash >= 0){ - // we handle only the next node: - path.buffer()[ixSlash] = '\0'; - } - // does the node exist? - if (lstat(path.str(), &info) != 0){ - // no, then we make it: - if (_mkdir(path.str(), ALLPERMS) != 0){ - if (logger != NULL) - logger->sayF(LOG_ERROR | CAT_FILE, LC_MAKE_DIR_1, - i18n("could not make directory $1 (errno: $2)")) - .arg(path.str()).arg(errno).end(); - rc = false; - break; - } else { -#if defined __linux__ - setProperties(path.str(), properties); -#endif - } - } - if (ixSlash >= 0){ - // restore the full path: - path.buffer()[ixSlash] = OS_SEPARATOR_CHAR; - } - } - return rc; -} - -/** - * Gets the arguments for the "list" command and execute this. - * - * @param argc the number of arguments - * @param argav the argument vector - */ -void ReDirSync::synchronize(int argc, const char* argv[]){ - ReDirEntryFilter_t filter; - const char* sep = OS_SEPARATOR; - struct stat info; - try { - time_t start = time(NULL); - m_programArgs.init(argc, argv); - ReByteBuffer buffer; - if (m_programArgs.getArgCount() < 2) - help(i18n("missing argument(s) (source / target)")); - ReByteBuffer target(m_programArgs.getArg(m_programArgs.getArgCount() - 1)); - if (target.endsWith(sep, 1)) - target.setLength(target.length() - 1); - if (stat(target.str(), &info) != 0) - help(i18n("target does not exist: $1"), target.str()); - else if (! S_ISDIR(info.st_mode)) - help(i18n("target is not a directory: $1"), target.str()); - size_t lengthTargetBase = target.length(); - bool addOnly = m_programArgs.getBool("add"); - int maxFileTimeDiff = m_programArgs.getInt("timediff"); - bool dry = m_programArgs.getBool("dry"); - bool ignoreDate = m_programArgs.getBool("ignoredate"); - bool mustExist = m_programArgs.getBool("mustexist"); - setFilterFromProgramArgs(filter); - int64_t sumSizes = 0; - int files = 0; - int treeFiles = 0; - int treeDirs = 0; - int64_t treeSumSizes = 0ll; - ReByteBuffer source, targetFile; - for (int ix = 0; ix < m_programArgs.getArgCount() - 1; ix++){ - source.set(m_programArgs.getArg(ix), -1); - target.setLength(lengthTargetBase); - bool endsWithSlash = source.endsWith(sep, 1); - if (endsWithSlash) - source.setLength(source.length() - 1); - if (stat(source.str(), &info) != 0) - help(i18n("source does not exist: $1"), source.str()); - else if (! S_ISDIR(info.st_mode)) - help(i18n("source is not a directory: $1"), source.str()); - if (! endsWithSlash){ - // the basename of the source will be appended to the target: - int startNode = source.rindexOf(sep, 1, 0, source.length() - 1); - target.append(OS_SEPARATOR, 1); - target.append(source.str() + startNode + 1, -1); - } - size_t ixSourceRelative = source.length(); - size_t ixTargetRelative = target.length(); - - ReTraverser traverser(source.str()); - traverser.setPropertiesFromFilter(&filter); - int level; - ReDirStatus_t* entry; - ReByteBuffer line; - while( (entry = traverser.nextFile(level, &filter)) != NULL){ - if (entry->isDirectory()) - continue; - // append the new relative path from source to target: - target.setLength(ixTargetRelative); - target.append(entry->m_path.str() + ixSourceRelative, -1); - if (stat(target.str(), &info) != 0) - makeDirWithParents(target, ixTargetRelative, traverser); - targetFile.set(target).append(entry->node(), -1); - const char* targetRelativePath = targetFile.str() + ixTargetRelative + 1; - bool exists = stat(targetFile.str(), &info) == 0; - if (! exists && mustExist){ - if (m_verboseLevel == V_CHATTER) - fprintf(m_output, "-ignored: %s does not exist\n", targetRelativePath); - continue; - } - if (exists){ - if (addOnly){ - if (m_verboseLevel >= V_CHATTER) - fprintf(m_output, "~ignored: %s exists\n", targetRelativePath); - continue; - } - if (ignoreDate && entry->fileSize() == info.st_size){ - if (m_verboseLevel >= V_CHATTER) - fprintf(m_output, "_ignored: %s same size\n", targetRelativePath); - continue; - } - // target younger than source? - int diff = int(info.st_mtime - entry->filetimeToTime(entry->modified())); - if (! ignoreDate && info.st_mtime - entry->filetimeToTime(entry->modified()) - <= maxFileTimeDiff) { - if (m_verboseLevel >= V_CHATTER) - fprintf(m_output, "=ignored: %s same time\n", targetRelativePath); - continue; - } - } - files++; - sumSizes += entry->fileSize(); - if (m_verboseLevel >= V_NORMAL) - fprintf(m_output, "%c%s%s\n", exists ? '!' : '+', targetRelativePath, - dry ? " would be copied" : ""); - if (! dry) - copyFile(entry, targetFile.str()); - } - treeFiles += traverser.files(); - treeDirs += traverser.directories(); - treeSumSizes+= traverser.sizes(); - } - if (m_verboseLevel >= V_SUMMARY){ - int duration = int(time(NULL) - start); - fprintf(m_output, i18n( - "=== copied: %02d:%02d sec %7d file(s) %12.6f MByte (%.3f MB/sec).\n" - "=== tree : %5d dir(s) %7d file(s) %12.6f MByte\n"), - duration / 60, duration % 60, files, sumSizes / 1E6, - sumSizes / 1E6 / (duration == 0 ? 1 : duration), - treeDirs, treeFiles, treeSumSizes / 1E6); - } - } catch(ReOptionException& exc){ - help(exc.getMessage()); - } -} - -/** - * Gets the arguments for the "batch" command and execute this. - * - * @param argc the number of arguments - * @param argav the argument vector - */ -void ReDirTools::batch(int argc, const char* argv[]){ - ReDirBatch batch; - batch.createBatch(argc, argv); -} - -/** - * Tests whether a abrevation of an argument is given. - * @param full the full name - * @param part the part to test - * @return true: part is a prefix of full - */ -static bool isArg(const char* full, const char* part){ - ReByteBuffer fullArg(full); - bool rc = fullArg.startsWith(part, -1); - return rc; -} - -/** - * Gets the arguments for the "help" command and execute this. - * - * @param argc the number of arguments - * @param argav the argument vector - */ -void ReDirTools::help(int argc, const char* argv[]){ - if (argc <= 1) - printField(s_helpSummary); - else { - argc--; - argv++; - const char* arg0 = argv[0]; - if (isArg("batch", arg0)){ - ReDirBatch batch; - batch.help(NULL); - } else if (isArg("list", arg0)){ - ReDirList list; - list.help(NULL); - } else if (isArg("help", arg0)) - printField(s_helpSummary); - else if (isArg("statistic", arg0)){ - ReDirStatistic stat; - stat.help(NULL); - } else if (isArg("test", arg0)){ - void testAll(); - testAll(); - } else - printf("+++ unknown sub command: %s\n", arg0); - } -} - -/** - * Gets the arguments for the "statistic" command and execute this. - * - * @param argc the number of arguments - * @param argav the argument vector - */ -void ReDirTools::list(int argc, const char* argv[]){ - ReDirList lister; - lister.list(argc, argv); -} - -/** - * Gets the arguments for any command and execute this. - * - * @param argc the number of arguments - * @param orgArgv the argument vector - */ -int ReDirTools::main(int argc, char* orgArgv[]){ - ReDirTools tools; - const char** argv = (const char**) orgArgv; - if (argc < 2){ - tools.help(0, argv); - exit(1); - } - argc--; - argv++; - const char* arg0 = argv[0]; - if (isArg("batch", arg0)) - tools.batch(argc, argv); - else if (isArg("list", arg0)) - tools.list(argc, argv); - else if (isArg("help", arg0)) - tools.help(argc, argv); - else if (isArg("statistic", arg0)) - tools.statistic(argc, argv); - else if (isArg("synchronize", arg0)) - tools.synchronize(argc, argv); - else if (isArg("test", arg0)){ - void testAll(); - testAll(); - }else - tools.usage("unknown command: ", argv[1]); - ReLogger::freeGlobalLogger(); - return 0; -} - -/** - * Gets the arguments for the "statistic" command and execute this. - * - * @param argc the number of arguments - * @param argav the argument vector - */ -void ReDirTools::statistic(int argc, const char* argv[]){ - ReDirStatistic statistic; - statistic.run(argc, argv); -} - -/** - * Gets the arguments for the "synchronize" command and execute this. - * - * @param argc the number of arguments - * @param argav the argument vector - */ -void ReDirTools::synchronize(int argc, const char* argv[]){ - ReDirSync sync; - sync.synchronize(argc, argv); -} - +bool ReTool::trace(const char* currentFile){ + ReByteBuffer buffer(" "); + int duration = int(time(NULL) - m_startTime); + buffer.appendInt(duration / 60).appendInt(duration % 60, ":%02d: "); + buffer.appendInt(m_files).append("/", 1).appendInt(m_traverser->directories()).append(" dir(s)"); + buffer.appendInt(m_files).append("/", 1).appendInt(m_traverser->files()).append(" dir(s)"); + buffer.append(currentFile); + fputs(buffer.str(), stdout); + return true; +} +/** + * Constructor. + */ +ReDirStatisticData::ReDirStatisticData() : + m_sizes(0), + m_files(0), + m_dirs(0), + m_path() +{ +} +/** + * Copy constructor. + * + * @param source the source to copy + */ +ReDirStatisticData::ReDirStatisticData(const ReDirStatisticData& source) : + m_sizes(source.m_sizes), + m_files(source.m_files), + m_dirs(source.m_dirs), + m_path(source.m_path) +{ +} +/** + * Assignment operator. + * + * @param source the source to copy + * @return the instance itself + */ +ReDirStatisticData& ReDirStatisticData::operator =(const ReDirStatisticData& source){ + m_sizes = source.m_sizes; + m_files = source.m_files; + m_dirs = source.m_dirs; + m_path = source.m_path; + return *this; +} + +/** + * Constructor. + */ +ReDirStatistic::ReDirStatistic(int deltaList, int deltaBuffer) : + ReDirOptions(s_statisticUsage, s_statisticExamples), + m_list(deltaList, deltaBuffer), + m_traceInterval(0), + m_lastTrace(0) +{ + // standard short options: D d O o P p T t v y Z z + m_programArgs.addBool("kbyte", + i18n("output format is ' ' (like unix 'du' command)"), + 'k', "kbyte", false); + addStandardFilterOptions(); +} +/** + * Destructor. + */ +ReDirStatistic::~ReDirStatistic(){ +} + +/** + * Adds the data from another instance. + * + * @param source the other instance + * @return the instance itself + */ +ReDirStatisticData& ReDirStatisticData::add(const ReDirStatisticData& source){ + m_sizes += source.m_sizes; + m_files += source.m_files; + m_dirs += source.m_dirs; + return *this; +} +/** + * Initializes the data of the instance. + */ +void ReDirStatisticData::clear(){ + m_sizes = 0; + m_files = 0; + m_dirs = 0; + m_path.setLength(0); +} + +/** + * Calculates the statistic of a directory tree. + * + * + */ +const ReStringList& ReDirStatistic::calculate(const char* base, int level, + void (*formatter)(const ReDirStatisticData& data, ReDirStatistic& parent, + ReByteBuffer& line)){ + ReDirEntryFilter_t filter; + ReTraverser traverser(base, this); + setFilterFromProgramArgs(filter); + traverser.setPropertiesFromFilter(&filter); + if (level > 1024) + level = 1024; + else if (level < 0) + level = 0; + m_list.clear(); + ReDirStatisticData** dataStack = new ReDirStatisticData*[level + 1]; + memset(dataStack, 0, sizeof dataStack[0] * (level + 1)); + dataStack[0] = new ReDirStatisticData(); + int topOfStack = 0; + ReDirStatus_t* entry; + int currentDepth = -1; + ReDirStatisticData* current = dataStack[0]; + current->m_path.set(base, -1); + ReByteBuffer line; + bool useFilter = filter.m_minSize > 0 || filter.m_maxSize != -1 + || filter.m_minAge != 0 || filter.m_maxAge != 0 + || m_nodePatterns.count() > 0; + while( (entry = traverser.rawNextFile(currentDepth))){ + if (currentDepth <= level && ! entry->m_path.equals(current->m_path)){ + if (currentDepth <= topOfStack){ + // close the entries of the stack above the new top level: + while(topOfStack >= currentDepth){ + // Add the data to the parent: + if (topOfStack > 0) + dataStack[topOfStack - 1]->add(*dataStack[topOfStack]); + // Append it to the result: + (*formatter)(*dataStack[topOfStack], *this, line); + m_list.append(line); + topOfStack--; + } + // We reuse the top of stack: + topOfStack++; + current = dataStack[topOfStack]; + // exchange the top of stack with the new found directory: + current->clear(); + current->m_path.set(entry->m_path.str(), entry->m_path.length());; + } else { + // set up a new stack entry: + if (currentDepth != topOfStack + 1) + assert(currentDepth == topOfStack + 1); + + topOfStack++; + if (dataStack[topOfStack] == NULL) + dataStack[topOfStack] = new ReDirStatisticData(); + else + dataStack[topOfStack]->clear(); + current = dataStack[topOfStack]; + current->m_path.set(entry->m_path.str(), entry->m_path.length()); + } + } + if (entry->isDirectory()){ + current->m_dirs++; + } else if (! useFilter || filter.match(*entry)){ + current->m_sizes += entry->fileSize(); + current->m_files++; + } + } + // close all dirs with parents: + while(topOfStack > 0){ + // Add the data to the parent: + dataStack[topOfStack - 1]->add(*dataStack[topOfStack]); + // Append it to the result: + (*formatter)(*dataStack[topOfStack], *this, line); + m_list.append(line); + topOfStack--; + } + // ... and the overall summery: + (*formatter)(*dataStack[0], *this, line); + m_list.append(line); + // free the resources: + for (int ix = 0; ix <= level; ix++) + delete dataStack[ix]; + delete[] dataStack; + return m_list; +} + +/** + * Build the statistic and print the results. + * + * @param arc count of arguments in argv + * @param argv the program arguments. + */ +void ReDirStatistic::run(int argc, const char* argv[]){ + time_t start = time(NULL); + try { + m_programArgs.init(argc, argv); + if (m_programArgs.getArgCount() < 1) + m_programArgs.help("statistic: missing path", false, stdout); + int depth = 1; + if (m_programArgs.getArgCount() > 1){ + const char* arg1 = m_programArgs.getArg(1); + if (ReStringUtils::lengthOfUnsigned(arg1, -1, (unsigned *) &depth) == 0) + m_programArgs.help("depth is not an integer", false, stdout); + } + ReDirStatistic statistic; + void (*proc) (const ReDirStatisticData& data, + ReDirStatistic& parent, ReByteBuffer& line) = &formatWithSizeFilesAndDirs; + if (m_programArgs.getBool("kbyte")) + proc = &formatLikeDu; + const ReStringList& list = statistic.calculate(m_programArgs.getArg(0), depth, proc); + ReByteBuffer buffer; + for (size_t ix = 0; ix < list.count(); ix++){ + buffer.set(list.strOf(ix), list.strLengthOf(ix)); + fprintf(m_output, "%s\n", buffer.str()); + } + if (m_verboseLevel >= V_SUMMARY){ + int duration = int(time(NULL) - start); + fprintf(m_output, "=== duration: "); + if (duration >= 3600) + fprintf(m_output, "%d:", duration / 3600); + fprintf(m_output, "%02d:%02d\n", duration % 3600 / 60, duration % 60); + } + } catch (ReOptionException& exc) { + m_programArgs.help(exc.getMessage(), false, stdout); + } +} +/** + * Formats a line like the du (disk usage) command. + * + * This is a possible parameter of ReDirStatistic::calculate(). + * + * @param data statistic data, including path name + * @param parent the caller (ReDirStatistic). This allows to deliver + * a context to this formatting routine (through derivation of + * ReDirStatistic) + * @param line OUT: the formatted line, the conclusion of the statistic data + */ +void formatLikeDu(const ReDirStatisticData& data, + ReDirStatistic& parent, ReByteBuffer& line){ + line.setLength(0); + // Round up to the next KiByte: + line.appendInt(int((data.m_sizes + 1023) / 1024)).append("\t").append(data.m_path); +} + +/** + * Formats a line in a standard way: MBytes, file count and directory count. + * + * This is a possible parameter of ReDirStatistic::calculate(). + * + * @param data statistic data, including path name + * @param parent the caller (ReDirStatistic). This allows to deliver + * a context to this formatting routine (through derivation of + * ReDirStatistic) + * @param line OUT: the formatted line, the conclusion of the statistic data + */ +void formatWithSizeFilesAndDirs(const ReDirStatisticData& data, + ReDirStatistic& parent, ReByteBuffer& line){ + line.setLength(0); + // Round up to the next KiByte: + char buffer[256]; + _snprintf(buffer, sizeof buffer, "%14.6f MB %7d %7d\t", + data.m_sizes / 1E6, data.m_files, data.m_dirs); + line.append(buffer, -1).append(data.m_path); +} +/** + * Prints a vector of lines. + * + * @param lines a vector of lines without newline ('\n') + */ +static void printField(const char** lines){ + for (int ix = 0; lines[ix] != NULL; ix++){ + printf("%s\n", lines[ix]); + } +} + +/** + * Prints an message how to use the statistic module and exits. + */ +void ReDirTools::statisticUsage(){ + ReDirStatistic statistic; + statistic.programArgs().help(NULL, false, stdout); +} + +/** + * Prints an message how to use the program and exits. + * + * @param msg an error message + * @param msg2 an addition to the error message or NULL + */ +void ReDirTools::usage(const char* msg, const char* msg2){ + printf ("Version: %s\n", m_version); + printf ("usage: dirtool \n"); + statisticUsage(); + if (msg != NULL) + printf ("+++ %s%s\n", msg, msg2 == NULL ? "" : msg2); + exit(1); +} + +/** + * Constructor. + */ +ReDirList::ReDirList() : + ReTool(s_listUsage, s_listExamples) +{ + m_programArgs.addBool("short", i18n("output is only path and basename"), + '1', "--short", false); + addStandardFilterOptions(); +} + +/** + * Gets the arguments for the "list" command and execute this. + * + * @param argc the number of arguments + * @param argav the argument vector + */ +void ReDirList::list(int argc, const char* argv[]){ + ReDirEntryFilter_t filter; + try { + time_t start = time(NULL); + m_programArgs.init(argc, argv); + bool shortOutput = m_programArgs.getBool("short"); + setFilterFromProgramArgs(filter); + bool noPath = m_programArgs.getArgCount() == 0; + ReByteBuffer bufferRights; + ReByteBuffer bufferTime; + int64_t sumSizes = 0; + int files = 0; + int dirs = 0; + + for (int ix = 0; noPath || ix < m_programArgs.getArgCount(); ix++){ + ReTraverser traverser(noPath ? "." : m_programArgs.getArg(ix)); + noPath = false; + traverser.setPropertiesFromFilter(&filter); + int level; + ReDirStatus_t* entry; + while( (entry = traverser.nextFile(level, &filter)) != NULL){ + if (entry->isDirectory()) + dirs++; + else{ + files++; + sumSizes += entry->fileSize(); + } + if (! printOneFile(entry)){ + if (shortOutput) + fprintf(m_output, "%s%s\n", entry->m_path.str(), entry->node()); + else + fprintf(m_output, "%s %12.6f %s %02x %s%s\n", + entry->rightsAsString(bufferRights), + entry->fileSize() / 1E6, + entry->filetimeAsString(bufferTime), + entry->type(), + entry->m_path.str(), entry->node()); + } + } + } + if (m_verboseLevel >= V_SUMMARY){ + int duration = int(time(NULL) - start); + fprintf(m_output, "+++ %d dirs and %d file(s) with %.6f MByte in %02d:%02d sec\n", + dirs, files, sumSizes / 1E6, duration / 60, duration % 60); + } + } catch(ReOptionException& exc){ + help(exc.getMessage()); + } +} + +/** + * Constructor. + */ +ReDirBatch::ReDirBatch() : + ReTool(s_batchUsage, s_batchExamples) +{ + // standard short options: D d O o P p T t v y Z z + m_programArgs.addString("first", + i18n("defines the first line of the output"), + '1', "first-line", true, +#if defined __linux__ + "#! /bin/sh" +#elif defined __WIN32__ + "rem this batch is created by dirtool" +#endif + ); + m_programArgs.addString("arguments", + i18n("template for the output line.\n" + "Possible placeholders: (e.g. e:\\data\\sample.txt)\n" + " !full!: e:\\data\\sample.txt\n" + " !path!: e:\\data\\\n" + " !basename!: sample.txt\n" + " !name!: sample\n" + " !ext!: .txt\n" + "example: --arguments='echo !basename! in !path! found'"), + 'a', "arguments", false, NULL); + m_programArgs.addString("script", + i18n("name of the script (starts each output line)"), + 'c', "script", false, NULL); +#if defined __WIN32__ + m_programArgs.addBool("isexe", + i18n("supresses the starting 'call' of each output line" + "neccessary if a *.exe will be called (instead of a *.bat)"), + 'x', "is-exe", false); +#endif + addStandardFilterOptions(); +} + +static void replaceMakros(const char* arguments, ReDirStatus_t* entry, const char* delim, ReByteBuffer& line){ + line.set(arguments, -1); + // we prepare the removal of unwanted delimiters in constructed placeholders: + // example: !path!!name!: without correction: "e:\\data\\""xxx" + // We want: "e:\\data\\xxx" + line.replaceAll("!!", 2, "!\x01!", 3); + ReByteBuffer replacement; + if (strstr(arguments, "!full!") != NULL){ + replacement.set(delim, -1).append(entry->m_path); + replacement.append(entry->node(), -1).append(delim, -1); + line.replaceAll("!full!", 6, replacement.str(), replacement.length()); + } + if (strstr(arguments, "!path!") != NULL){ + replacement.set(delim, -1).append(entry->m_path).append(delim, -1); + line.replaceAll("!path!", 6, replacement.str(), replacement.length()); + } + if (strstr(arguments, "!basename!") != NULL){ + replacement.set(delim, -1).append(entry->node(), -1).append(delim, -1); + line.replaceAll("!basename!", 10, replacement.str(), replacement.length()); + } + if (strstr(arguments, "!name!") != NULL){ + replacement.set(delim, -1).append(entry->node(), -1); + int ix = replacement.rindexOf(".", 1); + if (ix > 1) + replacement.setLength(ix); + replacement.append(delim, -1); + line.replaceAll("!name!", 6, replacement.str(), replacement.length()); + } + if (strstr(arguments, "!ext!") != NULL){ + replacement.set(delim, -1).append(entry->node(), -1); + int ix = replacement.rindexOf(".", 1); + if (ix > 1) + replacement.remove(1, ix - 1); + else + replacement.setLength(1); + replacement.append(delim, -1); + line.replaceAll("!ext!", 5, replacement.str(), replacement.length()); + } + // We remove the unwanted delimiters (see above): + ReByteBuffer buffer; + buffer.set(delim, -1).append("\x01", 1).append(delim, -1); + line.replaceAll(buffer.str(), buffer.length(), "", 0); +} +/** + * Gets the arguments for the "list" command and execute this. + * + * @param argc the number of arguments + * @param argav the argument vector + */ +void ReDirBatch::createBatch(int argc, const char* argv[]){ + ReDirEntryFilter_t filter; + try { + time_t start = time(NULL); + m_programArgs.init(argc, argv); + ReByteBuffer buffer; + ReByteBuffer arguments(m_programArgs.getString("arguments", buffer), -1); + ReByteBuffer script(m_programArgs.getString("script", buffer), -1); + if (arguments.length() + script.length() == 0) + help(i18n("one of the option must be set: -a (--arguments) or -c (--script)")); +#if defined __WIN32__ + bool isExe = m_programArgs.getBool("isexe"); +#endif + setFilterFromProgramArgs(filter); + if (m_programArgs.getArgCount() == 0) + help(i18n("no arguments given (missing path)")); + if (m_programArgs.getString("first", buffer)[0] != '\0') + printf("%s\n", buffer.str()); + int64_t sumSizes = 0; + int files = 0; + int dirs = 0; +#if defined __linux__ + const char* delim = "'"; +#elif defined __WIN32__ + const char* delim = "\""; +#endif + for (int ix = 0; ix < m_programArgs.getArgCount(); ix++){ + ReTraverser traverser(m_programArgs.getArg(ix)); + traverser.setPropertiesFromFilter(&filter); + int level; + ReDirStatus_t* entry; + ReByteBuffer line; + while( (entry = traverser.nextFile(level, &filter)) != NULL){ + if (entry->isDirectory()) + dirs++; + else{ + files++; + sumSizes += entry->fileSize(); + } + if (script.length() > 0){ + line.setLength(0); +#if defined __WIN32__ + if (! isExe) + line.append("call "); +#endif + line.append(script).append(" ").append(delim, -1); + line.append(entry->m_path).append(entry->node(), -1); + line.append(delim, -1); + } else { + replaceMakros(arguments.str(), entry, delim, line); + } + fprintf(m_output, "%s\n", line.str()); + } + } + if (m_verboseLevel >= V_SUMMARY){ + int duration = int(time(NULL) - start); +#if defined __linux__ + const char* prefix = "#"; +#elif defined __WIN32__ + const char* prefix = "rem"; +#endif + fprintf(m_output, "%s %d dir(s) and %d file(s) with %.6f MByte in %02d:%02d sec\n", + prefix, dirs, files, sumSizes / 1E6, duration / 60, duration % 60); + } + } catch(ReOptionException& exc){ + help(exc.getMessage()); + } +} + +/** + * creates a subdirectory (and the parent directories if neccessary. + * + * @param path the name of the subdir to create + */ +void ReDirSync::makeDirWithParents(ReByteBuffer& path, int minWidth, + ReTraverser& traverser){ + struct stat info; + bool endsWithSlash = path.str()[path.length() - 1] == OS_SEPARATOR_CHAR; + if (endsWithSlash) + path.setLength(path.length() - 1); + if (stat(path.str(), &info) != 0){ + ReFileProperties_t* props = NULL; +#if defined __linux__ + ReDirStatus_t* entry = traverser.topOfStack(); + props = entry == NULL ? NULL : &entry->m_status; +#endif + makeDirectory(path.str(), minWidth, props, ReLogger::globalLogger()); + } + if (endsWithSlash) + path.append(OS_SEPARATOR, 1); +} +/** + * Constructor. + */ +ReDirSync::ReDirSync() : + ReTool(s_syncUsage, s_syncExamples), + m_buffer() +{ + // standard short options: D d O o P p T t v y Z z + m_buffer.ensureSize(4u*1024u*1024u); + m_programArgs.addBool("add", + i18n("copies only files which does not exist on the target"), + 'a', "add", false); + m_programArgs.addBool("dry", + i18n("does nothing, but says what should be done"), + 'Y', "dry", false); + m_programArgs.addInt("timediff", + i18n("filetime difference is considered to be equal\n" + "if the difference is less than this value (in seconds)"), + 'I', "time-delta", 2); + m_programArgs.addBool("ignoredate", + i18n("the modification is recognized only by the different size (not time)"), + 'i', "ignore-time", false); + m_programArgs.addBool("mustexist", + i18n("files which don't exist on the target will not be copied"), + 'm', "must-exist", false); + addStandardFilterOptions(); +} + +/** + * Copies a file. + * + * @param entry the source file info + * @param target the name of the target file + */ +void ReDirSync::copyFile(ReDirStatus_t* entry, const char* target){ + ReFileProperties_t* props; +#ifdef __linux__ + props = &entry->m_status; +#else + ReFileProperties_t properties; + properties.m_modified = *entry->modified(); + properties.m_accessed = *entry->accessed(); + properties.m_size = entry->fileSize(); + props = &properties; +#endif + copyFile(entry->fullName(), props, target, m_buffer, + ReLogger::globalLogger()); +} + +/** + * Copies a file. + * + * @param source the source file name + * @param properties NULL or the properties of the source file + * @param target the name of the target file + * @param buffer OUT: the reading uses this buffer
+ * Set the capacity to make it more efficient + * @param logger NULL or the logger for error messages + * @return truesuccess
+ * falseerror occurred + */ +bool ReDirSync::copyFile(const char* source, ReFileProperties_t* properties, + const char* target, ReByteBuffer& buffer, ReLogger* logger){ + bool rc = false; +#ifdef __linux__ + struct stat info; + if (properties == NULL){ + if (stat(source, &info) == 0) + properties = &info; + else { + if (logger != NULL) + logger->sayF(LOG_ERROR | CAT_FILE, LC_COPY_FILE_1, + i18n("could not find: $ (errno: $2)")).arg(source) + .arg(errno).end(); + } + } + FILE* fpSource = fopen(source, "rb"); + if (fpSource == NULL){ + if (logger != NULL) + logger->sayF(LOG_ERROR | CAT_FILE, LC_COPY_FILE_2, + i18n("cannot open $1 (errno: $2)")) + .arg(source).arg(errno).end(); + } else { + FileSize_t size = properties == NULL ? 0x7fffffff : properties->st_size; + FILE* fpTarget = fopen(target, "w"); + if (fpTarget == NULL){ + if (logger != NULL) + logger->sayF(LOG_ERROR | CAT_FILE, LC_COPY_FILE_3, + i18n("cannot open $1 (errno: $2)")) + .arg(target).arg(errno).end(); + } else{ + while(size > 0){ + size_t blockSize = buffer.capacity(); + if ((int) blockSize > size) + blockSize = size; + if (fread(buffer.buffer(), blockSize, 1, fpSource) != 1){ + if (logger != NULL) + logger->sayF(LOG_ERROR | CAT_FILE, LC_COPY_FILE_5, + i18n("cannot read $1 (errno: $2)")) + .arg(source).arg(errno).end(); + break; + } + size_t written; + if ((written = fwrite(buffer.buffer(), 1, blockSize, + fpTarget)) != blockSize){ + if (logger != NULL) + logger->sayF(LOG_ERROR | CAT_FILE, LC_COPY_FILE_6, + i18n("cannot write $1 [$2] (errno: $3)")) + .arg(target).arg(written).arg(errno).end(); + break; + } + size -= blockSize; + } + rc = size == 0ll; + fclose(fpTarget); + if (properties != NULL) + setProperties(target, properties, logger); + } + fclose(fpSource); + } +#elif defined __WIN32__ + BOOL cancel = false; + rc = CopyFileExA(source, target, NULL, NULL, &cancel, COPY_FILE_NO_BUFFERING) != 0; + int errNo = 0; + if (! rc) + errNo = GetLastError(); +#endif + return rc; +} +/** + * Sets the file properties. + * + * @param fullName the name of the file + * @param properties the properties like times and rights + * @param logger NULL or the logger for error messages + * @return true: success
+ * false: error occurred + */ +bool ReDirSync::setProperties(const char* fullName, + ReFileProperties_t* properties, ReLogger* logger){ + bool rc = true; +#if defined __linux__ + struct utimbuf times; + times.actime = properties->st_atime; + times.modtime = properties->st_mtime; + if (utime(fullName, ×) != 0){ + if (logger != NULL) + logger->sayF(LOG_ERROR | CAT_FILE, LC_SET_PROPERTIES_1, + i18n("cannot change file times: $1 (errno: $2)")) + .arg(fullName).arg(errno).end(); + rc = false; + } + int rights = properties->st_mode & (S_IRWXO | S_IRWXG | S_IRWXU); + if (chmod(fullName, rights) != 0) { + if (logger != NULL) + logger->sayF(LOG_ERROR | CAT_FILE, LC_SET_PROPERTIES_2, + i18n("cannot change file modes: $1 (errno: $2)")) + .arg(fullName).arg(errno).end(); + rc = false; + } + if (chown(fullName, properties->st_uid, properties->st_gid) != 0){ + if (logger != NULL) + logger->sayF(LOG_ERROR | CAT_FILE, LC_SET_PROPERTIES_3, + i18n("cannot change file owner: $1 (errno: $2)")) + .arg(fullName).arg(errno).end(); + rc = false; + } +#endif + return rc; +} + +/** + * Creates a directory and its parents (if neccessary). + * + * @param directory the full name of the directory + * @param properties NULL or the properties of the new directory + * @param logger NULL or the logger for error messages + * @return truesuccess
+ * falseerror occurred + */ +bool ReDirSync::makeDirectory(const char* directory, int minLength, + ReFileProperties_t* properties, ReLogger* logger){ + bool rc = true; + ReByteBuffer path(directory); + int start = 0; +#if defined __WIN32__ + start = path.indexOf(':'); +#endif + int ixSlash = start < 0 ? 0 : start; + struct stat info; + // for all parents and the full path itself: + while(ixSlash >= 0){ + ixSlash = path.indexOf(OS_SEPARATOR_CHAR, ixSlash + 1); + if (ixSlash >= (int) path.length() - 1) + break; + // is the slash in front of the first node, e.g. 'e:\'? + if (ixSlash == start + 1) + // not a real node: take the next node + ixSlash = path.indexOf(OS_SEPARATOR_CHAR, ixSlash + 1); + if (ixSlash >= 0){ + // we handle only the next node: + path.buffer()[ixSlash] = '\0'; + } + // does the node exist? + if (lstat(path.str(), &info) != 0){ + // no, then we make it: + if (_mkdir(path.str(), ALLPERMS) != 0){ + if (logger != NULL) + logger->sayF(LOG_ERROR | CAT_FILE, LC_MAKE_DIR_1, + i18n("could not make directory $1 (errno: $2)")) + .arg(path.str()).arg(errno).end(); + rc = false; + break; + } else { +#if defined __linux__ + setProperties(path.str(), properties); +#endif + } + } + if (ixSlash >= 0){ + // restore the full path: + path.buffer()[ixSlash] = OS_SEPARATOR_CHAR; + } + } + return rc; +} + +/** + * Gets the arguments for the "list" command and execute this. + * + * @param argc the number of arguments + * @param argav the argument vector + */ +void ReDirSync::synchronize(int argc, const char* argv[]){ + ReDirEntryFilter_t filter; + const char* sep = OS_SEPARATOR; + struct stat info; + try { + time_t start = time(NULL); + m_programArgs.init(argc, argv); + ReByteBuffer buffer; + if (m_programArgs.getArgCount() < 2) + help(i18n("missing argument(s) (source / target)")); + ReByteBuffer target(m_programArgs.getArg(m_programArgs.getArgCount() - 1)); + if (target.endsWith(sep, 1)) + target.setLength(target.length() - 1); + if (stat(target.str(), &info) != 0) + help(i18n("target does not exist: $1"), target.str()); + else if (! S_ISDIR(info.st_mode)) + help(i18n("target is not a directory: $1"), target.str()); + size_t lengthTargetBase = target.length(); + bool addOnly = m_programArgs.getBool("add"); + int maxFileTimeDiff = m_programArgs.getInt("timediff"); + bool dry = m_programArgs.getBool("dry"); + bool ignoreDate = m_programArgs.getBool("ignoredate"); + bool mustExist = m_programArgs.getBool("mustexist"); + setFilterFromProgramArgs(filter); + int64_t sumSizes = 0; + int files = 0; + int treeFiles = 0; + int treeDirs = 0; + int64_t treeSumSizes = 0ll; + ReByteBuffer source, targetFile; + for (int ix = 0; ix < m_programArgs.getArgCount() - 1; ix++){ + source.set(m_programArgs.getArg(ix), -1); + target.setLength(lengthTargetBase); + bool endsWithSlash = source.endsWith(sep, 1); + if (endsWithSlash) + source.setLength(source.length() - 1); + if (stat(source.str(), &info) != 0) + help(i18n("source does not exist: $1"), source.str()); + else if (! S_ISDIR(info.st_mode)) + help(i18n("source is not a directory: $1"), source.str()); + if (! endsWithSlash){ + // the basename of the source will be appended to the target: + int startNode = source.rindexOf(sep, 1, 0, source.length() - 1); + target.append(OS_SEPARATOR, 1); + target.append(source.str() + startNode + 1, -1); + } + size_t ixSourceRelative = source.length(); + size_t ixTargetRelative = target.length(); + + ReTraverser traverser(source.str()); + traverser.setPropertiesFromFilter(&filter); + int level; + ReDirStatus_t* entry; + ReByteBuffer line; + while( (entry = traverser.nextFile(level, &filter)) != NULL){ + if (entry->isDirectory()) + continue; + // append the new relative path from source to target: + target.setLength(ixTargetRelative); + target.append(entry->m_path.str() + ixSourceRelative, -1); + if (stat(target.str(), &info) != 0) + makeDirWithParents(target, ixTargetRelative, traverser); + targetFile.set(target).append(entry->node(), -1); + const char* targetRelativePath = targetFile.str() + ixTargetRelative + 1; + bool exists = stat(targetFile.str(), &info) == 0; + if (! exists && mustExist){ + if (m_verboseLevel == V_CHATTER) + fprintf(m_output, "-ignored: %s does not exist\n", targetRelativePath); + continue; + } + if (exists){ + if (addOnly){ + if (m_verboseLevel >= V_CHATTER) + fprintf(m_output, "~ignored: %s exists\n", targetRelativePath); + continue; + } + if (ignoreDate && entry->fileSize() == info.st_size){ + if (m_verboseLevel >= V_CHATTER) + fprintf(m_output, "_ignored: %s same size\n", targetRelativePath); + continue; + } + // target younger than source? + int diff = int(info.st_mtime - entry->filetimeToTime(entry->modified())); + if (! ignoreDate && info.st_mtime - entry->filetimeToTime(entry->modified()) + <= maxFileTimeDiff) { + if (m_verboseLevel >= V_CHATTER) + fprintf(m_output, "=ignored: %s same time\n", targetRelativePath); + continue; + } + } + files++; + sumSizes += entry->fileSize(); + if (m_verboseLevel >= V_NORMAL) + fprintf(m_output, "%c%s%s\n", exists ? '!' : '+', targetRelativePath, + dry ? " would be copied" : ""); + if (! dry) + copyFile(entry, targetFile.str()); + } + treeFiles += traverser.files(); + treeDirs += traverser.directories(); + treeSumSizes+= traverser.sizes(); + } + if (m_verboseLevel >= V_SUMMARY){ + int duration = int(time(NULL) - start); + fprintf(m_output, i18n( + "=== copied: %02d:%02d sec %7d file(s) %12.6f MByte (%.3f MB/sec).\n" + "=== tree : %5d dir(s) %7d file(s) %12.6f MByte\n"), + duration / 60, duration % 60, files, sumSizes / 1E6, + sumSizes / 1E6 / (duration == 0 ? 1 : duration), + treeDirs, treeFiles, treeSumSizes / 1E6); + } + } catch(ReOptionException& exc){ + help(exc.getMessage()); + } +} + +/** + * Gets the arguments for the "batch" command and execute this. + * + * @param argc the number of arguments + * @param argav the argument vector + */ +void ReDirTools::batch(int argc, const char* argv[]){ + ReDirBatch batch; + batch.createBatch(argc, argv); +} + +/** + * Tests whether a abrevation of an argument is given. + * @param full the full name + * @param part the part to test + * @return true: part is a prefix of full + */ +static bool isArg(const char* full, const char* part){ + ReByteBuffer fullArg(full); + bool rc = fullArg.startsWith(part, -1); + return rc; +} + +/** + * Gets the arguments for the "help" command and execute this. + * + * @param argc the number of arguments + * @param argav the argument vector + */ +void ReDirTools::help(int argc, const char* argv[]){ + if (argc <= 1) + printField(s_helpSummary); + else { + argc--; + argv++; + const char* arg0 = argv[0]; + if (isArg("batch", arg0)){ + ReDirBatch batch; + batch.help(NULL); + } else if (isArg("list", arg0)){ + ReDirList list; + list.help(NULL); + } else if (isArg("help", arg0)) + printField(s_helpSummary); + else if (isArg("statistic", arg0)){ + ReDirStatistic stat; + stat.help(NULL); + } else if (isArg("test", arg0)){ + void testAll(); + testAll(); + } else + printf("+++ unknown sub command: %s\n", arg0); + } +} + +/** + * Gets the arguments for the "statistic" command and execute this. + * + * @param argc the number of arguments + * @param argav the argument vector + */ +void ReDirTools::list(int argc, const char* argv[]){ + ReDirList lister; + lister.list(argc, argv); +} + +/** + * Gets the arguments for any command and execute this. + * + * @param argc the number of arguments + * @param orgArgv the argument vector + */ +int ReDirTools::main(int argc, char* orgArgv[]){ + ReDirTools tools; + const char** argv = (const char**) orgArgv; + if (argc < 2){ + tools.help(0, argv); + exit(1); + } + argc--; + argv++; + const char* arg0 = argv[0]; + if (isArg("batch", arg0)) + tools.batch(argc, argv); + else if (isArg("list", arg0)) + tools.list(argc, argv); + else if (isArg("help", arg0)) + tools.help(argc, argv); + else if (isArg("statistic", arg0)) + tools.statistic(argc, argv); + else if (isArg("synchronize", arg0)) + tools.synchronize(argc, argv); + else if (isArg("test", arg0)){ + void testAll(); + testAll(); + }else + tools.usage("unknown command: ", argv[1]); + ReLogger::freeGlobalLogger(); + return 0; +} + +/** + * Gets the arguments for the "statistic" command and execute this. + * + * @param argc the number of arguments + * @param argav the argument vector + */ +void ReDirTools::statistic(int argc, const char* argv[]){ + ReDirStatistic statistic; + statistic.run(argc, argv); +} + +/** + * Gets the arguments for the "synchronize" command and execute this. + * + * @param argc the number of arguments + * @param argav the argument vector + */ +void ReDirTools::synchronize(int argc, const char* argv[]){ + ReDirSync sync; + sync.synchronize(argc, argv); +} +