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