File indexing completed on 2024-12-08 13:34:30

0001 /***************************************************************************
0002                           batchrenamer.cpp  -  description
0003                              -------------------
0004     begin                : Sat Aug 18 2001
0005     copyright            : (C) 2001 by Dominik Seichter
0006     email                : domseichter@web.de
0007  ***************************************************************************/
0008 
0009 /***************************************************************************
0010  *                                                                         *
0011  *   This program is free software; you can redistribute it and/or modify  *
0012  *   it under the terms of the GNU General Public License as published by  *
0013  *   the Free Software Foundation; either version 2 of the License, or     *
0014  *   (at your option) any later version.                                   *
0015  *                                                                         *
0016  ***************************************************************************/
0017 
0018 #include "batchrenamer.h"
0019 
0020 #include <QtGlobal>
0021 
0022 #ifdef Q_OS_WIN
0023 #include <windows.h>
0024 #endif
0025 
0026 // OS includes
0027 #include <stdio.h>
0028 #ifndef Q_OS_WIN
0029 #include <unistd.h>
0030 #endif
0031 
0032 #include "../config-krename.h"
0033 
0034 // chmod:
0035 #include <sys/types.h>
0036 #include <sys/stat.h>
0037 
0038 // QT includes
0039 #include <QTextStream>
0040 
0041 // KDE includes
0042 #include <kio/job.h>
0043 #include <KIO/MkpathJob>
0044 #include <KJobWidgets>
0045 
0046 // Own includes
0047 #include "progressdialog.h"
0048 #include "pluginloader.h"
0049 #include "plugin.h"
0050 
0051 using namespace KIO;
0052 
0053 static bool isToken(const QChar &token)
0054 {
0055     const QChar tokens[] = {
0056         QChar('&'),
0057         QChar('$'),
0058         QChar('%'),
0059         QChar('#'),
0060         QChar('['),
0061         QChar(']'),
0062         QChar('\\'),
0063         QChar('/'),
0064         QChar('{'),
0065         QChar('}'),
0066         QChar('*')
0067     };
0068     const int count = 11;
0069 
0070     for (int i = 0; i < count; i++)
0071         if (token == tokens[i]) {
0072             return true;
0073         }
0074 
0075     return false;
0076 }
0077 
0078 static int getNextToken(const QString &text, QString &token, int pos = 0)
0079 {
0080     bool escaped = false;
0081     token.clear();
0082 
0083     if (pos < 0) {
0084         return -1;
0085     }
0086 
0087     while (pos < text.length()) {
0088         if (!escaped && text[pos] == QChar('\\')) {
0089             escaped = true;
0090         } else if (!escaped && isToken(text[pos])) {
0091             token = text[pos];
0092             return ++pos;
0093         } else {
0094             escaped = false;
0095         }
0096 
0097         ++pos;
0098     }
0099 
0100     return -1;
0101 }
0102 
0103 BatchRenamer::BatchRenamer()
0104     : m_index(0), m_step(1), m_files(nullptr), m_renameMode(eRenameMode_Rename)
0105 {
0106     m_counter_index = 0;
0107     m_reset         = false;
0108     m_overwrite     = false;
0109 }
0110 
0111 BatchRenamer::~BatchRenamer()
0112 {
0113 }
0114 
0115 void BatchRenamer::processFilenames()
0116 {
0117     m_counters.clear();
0118 
0119     for (unsigned int i = 0; i < static_cast<unsigned int>(m_files->count()); i++) {
0120         m_counter_index = 0;
0121         if (m_renameMode == eRenameMode_Rename) { // final Path = source Path
0122             (*m_files)[i].setDstDirectory((*m_files)[i].srcDirectory());
0123 
0124             QUrl url = (*m_files)[i].srcUrl();
0125             url = url.adjusted(QUrl::RemoveFilename);
0126 
0127             (*m_files)[i].setDstUrl(url);
0128 
0129         } else {
0130             (*m_files)[i].setDstUrl(m_destination);
0131             (*m_files)[i].setDstDirectory(m_destination.path());
0132         }
0133 
0134         if (i > 0 && m_reset) {
0135             findCounterReset(i);
0136         }
0137 
0138         //qDebug("SRCFILENAME       : %s", (*m_files)[i].srcFilename().toUtf8().data() );
0139         //qDebug("DSTFILENAME SHOULD: %s", processString( text, (*m_files)[i].srcFilename(), i ).toUtf8().data() );
0140         (*m_files)[i].setDstFilename(processString(text, (*m_files)[i].srcFilename(), i));
0141         //qDebug("DSTFILENAME IS    : %s", (*m_files)[i].dstFilename().toUtf8().data());
0142         (*m_files)[i].setDstExtension(processString(extext, (*m_files)[i].srcExtension(), i));
0143 
0144         // Let's run the plugins that change the final filename,
0145         // i.e the encodingsplugin
0146         int errors = 0;
0147         QString name = executePlugin(i, (*m_files)[i].dstFilename(), ePluginType_Filename, errors, nullptr);
0148         if (!name.isNull()) {
0149             (*m_files)[i].setDstFilename(name);
0150         }
0151 
0152         /*
0153          * take care of renamed directories and
0154          * correct the paths of their contents
0155          */
0156         if ((m_renameMode == eRenameMode_Rename ||
0157                 m_renameMode == eRenameMode_Move) &&
0158                 (*m_files)[i].isDirectory()) {
0159             const QString topDir  = (*m_files)[i].realSrcDirectory() + '/' + (*m_files)[i].srcFilename();
0160             const QString replace = (*m_files)[i].dstDirectory() + '/' + (*m_files)[i].dstFilename();
0161 
0162             for (int z = i + 1; z < m_files->count(); z++) {
0163                 const QString &dir = (*m_files)[z].realSrcDirectory();
0164                 if (dir.startsWith(topDir)) {
0165                     QString newDir = replace + dir.right(dir.length() - topDir.length());
0166                     if (newDir != dir) {
0167                         (*m_files)[z].setOverrideSrcDirectory(newDir);
0168                     }
0169                 }
0170             }
0171         }
0172 
0173 #if 0
0174         if (m_files[i].dir && (m_mode == RENAME || m_mode == MOVE)) {
0175             for (unsigned int c = i; c < m_files.count(); c++) {
0176                 if (m_files[c].src.directory.left(m_files[i].src.name.length() + 1)
0177                         == (m_files[i].src.name + "/")) {
0178 
0179                     m_files[c].src.directory.replace(0, m_files[i].src.name.length(), m_files[i].dst.name);
0180                     m_files[c].src.url.setPath(BatchRenamer::buildFilename(&m_files[c].src, true));
0181                 }
0182             }
0183         }
0184 #endif // 0
0185     }
0186 }
0187 
0188 void BatchRenamer::processFiles(ProgressDialog *p)
0189 {
0190     int     errors = 0;
0191     QUrl    dest   = (*m_files)[0].dstUrl();
0192     // TODO: error handling if dest is empty
0193 
0194     // Give the user some information...
0195     p->setProgressTotalSteps(m_files->count());
0196     p->setProgress(0);
0197     p->setDestination(dest);
0198 
0199     switch (m_renameMode) {
0200     default:
0201     case eRenameMode_Rename:
0202         p->print(i18n("Input files will be renamed."));
0203         break;
0204     case eRenameMode_Copy:
0205         p->print(i18n("Files will be copied to: %1", dest.toDisplayString(QUrl::PreferLocalFile)));
0206         break;
0207     case eRenameMode_Move:
0208         p->print(i18n("Files will be moved to: %1", dest.toDisplayString(QUrl::PreferLocalFile)));
0209         break;
0210     case eRenameMode_Link:
0211         p->print(i18n("Symbolic links will be created in: %1", dest.toDisplayString(QUrl::PreferLocalFile)));
0212         break;
0213     }
0214 
0215     for (unsigned int i = 0; i < static_cast<unsigned int>(m_files->count()); i++) {
0216         QUrl    dstUrl    = this->buildDestinationUrl((*m_files)[i]);
0217 
0218         //p->print( QString( "%1 -> %2" ).arg( (*m_files)[i].srcUrl().prettyUrl() ).arg( dstUrl.toDisplayString() ) );
0219         p->setProgress(i + 1);
0220 
0221         if (p->wasCancelled()) {
0222             break;
0223         }
0224 
0225         // Assemble filenames
0226         createMissingSubDirs(dstUrl, p);
0227 
0228         KIO::JobFlags flags  = (m_overwrite ? KIO::Overwrite : KIO::DefaultFlags) | KIO::HideProgressInfo;
0229         KIO::Job     *job    = nullptr;
0230         const QUrl &srcUrl = (*m_files)[i].srcUrl();
0231         if (srcUrl == dstUrl) {
0232             p->warning(i18n("Cannot rename: source and target filename are equal: %1", srcUrl.toDisplayString(QUrl::PreferLocalFile)));
0233             //(*m_files)[i].setError( 1 );
0234             //errors++;
0235             continue;
0236         }
0237 
0238         switch (m_renameMode) {
0239         default:
0240         case eRenameMode_Rename:
0241         case eRenameMode_Move:
0242             job = KIO::file_move(srcUrl, dstUrl, -1, flags);
0243             break;
0244         case eRenameMode_Copy:
0245             job = KIO::file_copy(srcUrl, dstUrl, -1, flags);
0246             break;
0247         case eRenameMode_Link: {
0248             if (!srcUrl.isLocalFile()) {
0249                 // We can only do symlinks to local urls
0250                 p->error(i18n("Cannot create symlink to non-local URL: %1", srcUrl.toDisplayString()));
0251                 (*m_files)[i].setError(1);
0252                 errors++;
0253             } else {
0254                 job = KIO::symlink(srcUrl.path(), dstUrl, flags);
0255             }
0256 
0257             break;
0258         }
0259         }
0260 
0261         if (job) {
0262             KJobWidgets::setWindow(job, p);
0263             if (!job->exec()) {
0264                 p->error(i18n("Error renaming %2 (to %1)",
0265                               dstUrl.toDisplayString(QUrl::PreferLocalFile),
0266                               srcUrl.toDisplayString(QUrl::PreferLocalFile)));
0267                 (*m_files)[i].setError(job->error());
0268                 errors++;
0269             }
0270         }
0271 
0272         /*
0273          * The renamed file should be on its correct location now,
0274          * so that we can call the last plugins (e.g. for changing permissions)
0275          *
0276          * Remember, the token argument is the filename for this type of plugins!
0277          *
0278          * If the return value is not empty an error has occurred!
0279          * The plugin should return an error message in this case!
0280          */
0281         int errorCount = 0;
0282         this->executePlugin(i, dstUrl.path(), ePluginType_File, errorCount, p);
0283         errors += errorCount;
0284     }
0285 
0286     if (errors > 0) {
0287         p->warning(i18np("%1 error occurred.", "%1 errors occurred.", errors));
0288     }
0289 
0290     p->print(i18n("KRename finished the renaming process."), "krename");
0291     p->print(i18n("Press close to quit."));
0292     bool enableUndo = (m_renameMode != eRenameMode_Copy);
0293     p->renamingDone(true, enableUndo, this, errors);
0294 
0295 #if 0
0296     delete object;
0297     t.start();
0298 
0299     m_counters.clear();
0300 
0301     for (unsigned int i = 0; i < m_files->count(); i++) {
0302         m_counter_index = 0;
0303 
0304         if (m_mode == RENAME) { // final Path = source Path
0305             m_files[i].dst.directory = m_files[i].src.directory;
0306             m_files[i].dst.url = m_files[i].src.url;
0307             m_files[i].dst.url.setFileName(QString::null);
0308         } else {
0309             m_files[i].dst.directory = m_destination.path();
0310             m_files[i].dst.url = m_destination;
0311         }
0312 
0313         if (i == 0) {
0314             p->setDestination(m_files[i].dst.url);
0315         } else {
0316             if (m_reset) {
0317                 findCounterReset(i);
0318             }
0319         }
0320 
0321         m_files[i].dst.name = processString(text, m_files[i].src.name, i);
0322         if (!extext.isEmpty()) {
0323             m_files[i].dst.extension = processString(extext, m_files[i].src.extension, i);
0324         }
0325 
0326         // Assemble filenames
0327         parseSubdirs(&m_files[i]);
0328         // TODO: DOM
0329         // ESCAPE HERE
0330 
0331         m_files[i].src.name = BatchRenamer::buildFilename(&m_files[i].src, true);
0332 
0333         // Let's run the plugins that change the final filename,
0334         // i.e the encodingsplugin
0335         m_files[i].dst.name = parsePlugins(i, m_files[i].dst.name, TYPE_FINAL_FILENAME);
0336 
0337         m_files[i].dst.name = BatchRenamer::buildFilename(&m_files[i].dst, true);
0338 
0339         /*
0340          * take care of renamed directories and
0341          * correct the paths of their contents
0342          */
0343         if (m_files[i].dir && (m_mode == RENAME || m_mode == MOVE)) {
0344             for (unsigned int c = i; c < m_files.count(); c++) {
0345                 if (m_files[c].src.directory.left(m_files[i].src.name.length() + 1)
0346                         == (m_files[i].src.name + "/")) {
0347 
0348                     m_files[c].src.directory.replace(0, m_files[i].src.name.length(), m_files[i].dst.name);
0349                     m_files[c].src.url.setPath(BatchRenamer::buildFilename(&m_files[c].src, true));
0350                 }
0351             }
0352         }
0353     }
0354 
0355     p->print(QString(i18n("Filenames Processed after %1 seconds.", t.elapsed() / 1000)));
0356 
0357     work(p);
0358 #endif // 0
0359 }
0360 
0361 void BatchRenamer::undoFiles(ProgressDialog *p)
0362 {
0363     int     errors = 0;
0364     QUrl    dest   = (*m_files)[0].dstUrl();
0365 
0366     // Give the user some information...
0367     p->setProgressTotalSteps(m_files->count());
0368     p->setProgress(0);
0369     p->setDestination(dest);
0370     p->print(i18n("Undoing all renamed files."));
0371 
0372     for (unsigned int i = 0; i < static_cast<unsigned int>(m_files->count()); i++) {
0373         QUrl dstUrl = this->buildDestinationUrl((*m_files)[i]);
0374 
0375         //p->print( QString( "%1 -> %2" ).arg( (*m_files)[i].srcUrl().prettyUrl() ).arg( dstUrl.toDisplayString() ) );
0376         p->setProgress(i + 1);
0377 
0378         if (p->wasCancelled()) {
0379             break;
0380         }
0381 
0382         KIO::JobFlags flags = (m_overwrite ? KIO::Overwrite : KIO::DefaultFlags) | KIO::HideProgressInfo;
0383         KIO::Job     *job   = nullptr;
0384         switch (m_renameMode) {
0385         default:
0386         case eRenameMode_Rename:
0387         case eRenameMode_Move:
0388             job = KIO::file_move(dstUrl, (*m_files)[i].srcUrl(), -1, flags);
0389             break;
0390         case eRenameMode_Link:
0391             // In case of link delete created file
0392             job = KIO::file_delete(dstUrl, KIO::HideProgressInfo);
0393             break;
0394         case eRenameMode_Copy: // no undo possible
0395             // TODO: Maybe we should delete the created files
0396             break;
0397         }
0398 
0399         if (job) {
0400             KJobWidgets::setWindow(job, p);
0401             if (!job->exec()) {
0402                 p->error(i18n("Error during undoing %1", dstUrl.toDisplayString(QUrl::PreferLocalFile)));
0403                 (*m_files)[i].setError(job->error());
0404                 errors++;
0405             }
0406         }
0407 
0408     }
0409 
0410     if (errors > 0) {
0411         p->warning(i18np("%1 error occurred.", "%1 errors occurred.", errors));
0412     }
0413 
0414     p->print(i18n("KRename finished the undo process."), "krename");
0415     p->print(i18n("Press close to quit."));
0416     p->renamingDone(false, false, this, errors);   // do not allow undo from undo
0417 
0418 }
0419 
0420 QString BatchRenamer::processBrackets(QString text, int *length, const QString &oldname, int index)
0421 {
0422     int  pos   = 0;
0423     QString token;
0424     QString result;
0425 
0426     *length = 0;
0427 
0428     // MSG: qDebug("processBrackets: %s\n", text.toUtf8().data() );
0429     while ((pos = getNextToken(text, token, pos)) != -1) {
0430         if (token == "[") {
0431             int localLength = 0;
0432             QString substitute = processBrackets(text.right(text.length() - pos), &localLength, oldname, index);
0433             text.replace(pos - 1, localLength, substitute);
0434             // MSG: qDebug("substituted: %s\n", text.toUtf8().data() );
0435             // Assure that *length does not become negative,
0436             // this will cause infinite loops
0437             if (localLength < substitute.length()) {
0438                 *length += localLength;
0439             } else {
0440                 *length += (localLength - substitute.length());
0441             }
0442         } else if (token == "]") {
0443             // Done with this token
0444             // MSG: qDebug("END: %s\n", text.left( pos - 1 ).toUtf8().data() );
0445             result = findToken(oldname, text.left(pos - 1), index);
0446             *length += pos + 1;
0447             break;
0448         }
0449     }
0450     // MSG: qDebug("processedBrackets: %s\n", result.toUtf8().data() );
0451 
0452     /*
0453 
0454       if( pos != -1 )
0455       {
0456       result = findToken( oldname, text.left( pos - 1 ), index );
0457       *length = pos+1; // skip any closing bracket
0458       }
0459     */
0460     return result;
0461 }
0462 
0463 QString BatchRenamer::processNumber(int length, const QString &appendix)
0464 {
0465     tCounterValues countervalues;
0466     countervalues.start = m_index;
0467     countervalues.step = m_step;
0468 
0469     if (!appendix.isEmpty()) {
0470         bool ok = false;
0471         int tmp = appendix.section(';', 0, 0).toInt(&ok);     // first section = start index
0472         if (ok) {
0473             countervalues.start = tmp;
0474         }
0475 
0476         tmp = appendix.section(';', 1, 1).toInt(&ok);    // second section = stepping
0477         if (ok) {
0478             countervalues.step = tmp;
0479         }
0480     }
0481 
0482     if ((signed int)m_counters.count() <= m_counter_index) {
0483         countervalues.value = countervalues.start - countervalues.step;
0484         // other wise the counter would start at:
0485         // start + step instead of start
0486         m_counters.append(countervalues);
0487     }
0488 
0489     do {
0490         m_counters[m_counter_index].value += m_counters[m_counter_index].step;
0491     } while (m_skip.contains(m_counters[m_counter_index].value));
0492 
0493     QString number;
0494     number.sprintf("%0*i", length, m_counters[m_counter_index].value);
0495 
0496     ++m_counter_index;
0497     return number;
0498 }
0499 
0500 QString BatchRenamer::processString(QString text, const QString &originalName, int index, bool doFindReplace)
0501 {
0502     QString oldname = originalName;
0503     doEscape(oldname);
0504 
0505     // Parse into tokens
0506     int pos = 0;
0507     QString token;
0508     while ((pos = getNextToken(text, token, pos)) != -1) {
0509 
0510         // Handle simple tokens
0511         if (token == "$") {
0512             text.replace(pos - 1, token.length(), oldname);
0513             pos += oldname.length() - 1;
0514         } else if (token == "%") {
0515             text.replace(pos - 1, token.length(), oldname.toLower());
0516             pos += oldname.length() - 1;
0517         } else if (token == "&") {
0518             text.replace(pos - 1, token.length(), oldname.toUpper());
0519             pos += oldname.length() - 1;
0520         } else if (token == "*") {
0521             QString tmp = capitalize(oldname);
0522 
0523             text.replace(pos - 1, token.length(), tmp);
0524             pos += tmp.length() - 1;
0525         } else if (token == "[") {
0526             int length = 0;
0527             QString substitute = processBrackets(text.right(text.length() - pos), &length, oldname, index);
0528             text.replace(pos - 1, length, substitute);
0529             if (substitute.length() > 0) {
0530                 pos += substitute.length() - 1;
0531             }
0532         } else if (token == "]") {
0533             // Ignore
0534         } else if (token == "#") {
0535             int curPos = pos;
0536             int count  = 1;
0537             while (text[curPos] == '#') {
0538                 ++curPos;
0539                 count++;
0540             }
0541 
0542             int length = curPos - pos + 1;
0543             int appendixLength = 0;
0544             QString appendix;
0545             if (text[curPos] == '{') {
0546                 int     appendixPos = curPos + 1;
0547                 QString appendixToken;
0548                 while ((appendixPos = getNextToken(text, appendixToken, appendixPos)) != -1) {
0549                     if (appendixToken == "}") {
0550                         break;
0551                     }
0552                 }
0553 
0554                 if (appendixPos == -1) {
0555                     // Do go into endless loop if token is not completed correctly
0556                     appendixPos = text.length();
0557                 }
0558 
0559                 // -2 because we have to go, before the current found token
0560                 appendix = text.mid(curPos + 1, appendixPos - curPos - 2);
0561                 appendixLength = appendixPos - curPos;
0562             }
0563 
0564             QString number   = processNumber(count, appendix);
0565             text.replace(pos - 1, (length + appendixLength), number);
0566 
0567             if (number.length() > 0) {
0568                 pos += number.length() - 1;
0569             }
0570         }
0571     }
0572 
0573     //text = parsePlugins( i, text, TYPE_TOKEN );
0574 
0575     /*
0576      * Replace after Plugins !
0577      * Replace should be the last the
0578      * before re-escaping tokens !
0579      */
0580     if (doFindReplace) {
0581         text = findReplace(text, originalName, index);
0582     }
0583     text = unEscape(text);
0584     return text;
0585 }
0586 
0587 QString BatchRenamer::capitalize(const QString &text) const
0588 {
0589     QString tmp = text.toLower();
0590     if (tmp[0].isLetter()) {
0591         tmp[0] = tmp[0].toUpper();
0592     }
0593 
0594     for (int i = 0; i < tmp.length(); i++)
0595         if (tmp[i + 1].isLetter() && !tmp[i].isLetter() &&
0596                 tmp[i] != '\'' && tmp[i] != '?' && tmp[i] != '`') {
0597             tmp[i + 1] = tmp[i + 1].toUpper();
0598         }
0599 
0600     return tmp;
0601 }
0602 
0603 QString BatchRenamer::executePlugin(int index, const QString &filenameOrPath, int type, int &errorCount, ProgressDialog *p)
0604 {
0605     const QList<Plugin *> &plugins = PluginLoader::Instance()->plugins();
0606     QList<Plugin *>::const_iterator it = plugins.begin();
0607 
0608     errorCount = 0;
0609     QString ret = filenameOrPath;
0610     while (it != plugins.end()) {
0611         if ((*it)->isEnabled() && ((*it)->type() & type)) {
0612             // Every plugin should use the return value of the previous as the new filename to work on
0613             ret = (*it)->processFile(this, index, ret, static_cast<EPluginType>(type));
0614             if (type == ePluginType_File) {
0615                 if (! ret.isEmpty()) {
0616                     // An error occurred -> report it
0617                     if (p != nullptr) {
0618                         p->error(ret);
0619                     }
0620                     ++errorCount;
0621                 }
0622 
0623                 ret = filenameOrPath;
0624             }
0625         }
0626 
0627         ++it;
0628     }
0629 
0630     return ret;
0631 }
0632 
0633 void BatchRenamer::work(ProgressDialog *)
0634 {
0635 #if 0
0636     // TODO: use CopyJob here
0637 
0638     FileOperation fop;
0639     QFile *fundo(NULL);
0640     QTextStream *tundo(NULL);
0641 
0642     if (undo) {
0643         // Create header for undo script
0644         fundo = new QFile(m_undoScript);
0645         if (fundo->open(IO_WriteOnly)) {
0646             tundo = new QTextStream(fundo);
0647             writeUndoScript(tundo);
0648         } else {
0649             undo = false;
0650             p->error(i18n("Cannot create undo script: %1", fundo->name()));
0651             delete fundo;
0652         }
0653     }
0654 
0655     int error = 0;
0656     RenamedList *renamedFiles = new RenamedList[m_files.count()];
0657     p->setProgressTotalSteps(m_files.count() + 1);
0658 
0659     /*
0660      * Give the user some information...
0661      */
0662     if (m_mode == COPY) {
0663         p->print(i18n("Files will be copied to: %1", m_files[0].dst.directory));
0664     } else if (m_mode == MOVE) {
0665         p->print(i18n("Files will be moved to: %1", m_files[0].dst.directory));
0666     } else if (m_mode == LINK) {
0667         p->print(i18n("Symbolic links will be created in: %1", m_files[0].dst.directory));
0668     } else if (m_mode == RENAME) {
0669         p->print(i18n("Input files will be renamed."));
0670     }
0671 
0672     unsigned int i;
0673     for (i = 0; i < m_files.count(); i++) {
0674         p->setProgress(i + 1);
0675 
0676         if (p->wasCancelled()) {
0677             break;
0678         }
0679 
0680         KURL src = m_files[i].src.url;
0681         KURL dst = m_files[i].dst.url;
0682         dst.setPath(m_files[i].dst.name);
0683 
0684         renamedFiles[i].src = src;
0685         renamedFiles[i].dst = dst;
0686         renamedFiles[i].dir = m_files[i].dir;
0687 
0688         FileOperation fop;
0689         if (!fop.start(src, dst, m_mode, overwrite)) {
0690             p->error(fop.error());
0691             renamedFiles[i].error = true;
0692             error++;
0693             continue;
0694         } else {
0695             renamedFiles[i].error = false;
0696         }
0697 
0698         // TODO: overwriting of files!
0699         /*
0700          * The renamed file should be on its correct location now,
0701          * so that we can call the last plugins (e.g. for changing permissions)
0702          *
0703          * Remember, the token argument is the filename for this type of plugins!
0704          *
0705          * If the return value is not empty an error has occurred!
0706          * The plugin should return an error message in this case!
0707          */
0708 
0709         QString eplug = parsePlugins(i, QString::null, TYPE_FINAL_FILE);
0710         if (!eplug.isEmpty()) {
0711             p->error(eplug);
0712             error++;
0713         }
0714 
0715         /* Create the undo script now */
0716         if (undo)
0717             if (dst.isLocalFile() && src.isLocalFile()) {
0718                 // Plugins ???
0719                 (*tundo) << "echo \"" << dst.fileName()
0720                          << " -> " << src.fileName() << "\"" << endl;
0721                 (*tundo) << "mv -f \"" << m_files[i].dst.name
0722                          << "\" \"" << m_files[i].src.name << "\"" << endl;
0723             } else {
0724                 p->warning(i18n("Undo is not possible for remote file: %1", dst.prettyURL()));
0725             }
0726 
0727     }
0728 
0729     if (!p->wasCancelled()) {
0730         QPtrListIterator<PluginLoader::PluginLibrary> it(plug->libs);
0731         for (; it.current(); ++it) {
0732             if ((*it)->usePlugin) {
0733                 (*it)->plugin->finished();
0734             }
0735         }
0736     }
0737 
0738     const QString m = i18n("Renamed %1 files successfully.", i - error);
0739     (i - error) ? p->print(m) : p->warning(m);
0740 
0741     if (error > 0) {
0742         p->warning(i18np("%1 error occurred.", "%1 errors occurred.", error));
0743     }
0744 
0745     p->print(i18n("Elapsed time: %1 seconds", t.elapsed() / 1000), "kalarm");
0746     p->print(i18n("KRename finished the renaming process."), "krename");
0747     p->print(i18n("Press close to quit."));
0748     p->setRenamedFiles(renamedFiles, m_files.count());
0749 
0750     if (undo) {
0751         (*tundo) << endl << "echo \"Finished undoing " << m_files.count() << " actions.\"" << endl;
0752         delete tundo;
0753         fundo->close();
0754 
0755         // Make fundo exuteable
0756         if (chmod((const char *)m_undoScript, (unsigned int) S_IRUSR | S_IWUSR | S_IXUSR)) {
0757             p->error(i18n("Cannot set executable bit on undo script."));
0758         }
0759         delete fundo;
0760     }
0761 
0762     p->done(error, i - error, m_mode == MOVE || m_mode == RENAME);
0763     m_files.clear();
0764     delete []renamedFiles;
0765     delete this;
0766 #endif // 0
0767 }
0768 
0769 const QUrl BatchRenamer::buildDestinationUrl(const KRenameFile &file) const
0770 {
0771     QUrl    dstUrl    = file.dstUrl();
0772     QString directory = file.dstDirectory();
0773     QString filename  = file.dstFilename();
0774     QString extension = file.dstExtension();
0775     QString manual    = file.manualChanges();
0776 
0777     if (!extension.isEmpty()) {
0778         filename += '.';
0779         filename += extension;
0780     }
0781 
0782     if (!manual.isNull()) {
0783         filename = manual;
0784     }
0785 
0786     dstUrl.setPath(directory + '/' +  filename);
0787 
0788     return dstUrl;
0789 }
0790 
0791 void BatchRenamer::escape(QString &text, const QString &token, const QString &sequence)
0792 {
0793     text.replace(token, sequence);
0794 }
0795 
0796 QString &BatchRenamer::doEscape(QString &text)
0797 {
0798     BatchRenamer::escape(text, "\\", "\\\\");
0799     BatchRenamer::escape(text, "&", "\\&");
0800     BatchRenamer::escape(text, "$", "\\$");
0801     BatchRenamer::escape(text, "%", "\\%");
0802     BatchRenamer::escape(text, "#", "\\#");
0803     BatchRenamer::escape(text, "[", "\\[");
0804     BatchRenamer::escape(text, "]", "\\]");
0805     BatchRenamer::escape(text, "/", "\\/");
0806     BatchRenamer::escape(text, "{", "\\{");
0807     BatchRenamer::escape(text, "}", "\\}");
0808     BatchRenamer::escape(text, "*", "\\*");
0809 
0810     return text;
0811 }
0812 
0813 QString &BatchRenamer::unEscape(QString &text)
0814 {
0815     BatchRenamer::escape(text, "\\\\", "\\");
0816     BatchRenamer::escape(text, "\\&", "&");
0817     BatchRenamer::escape(text, "\\$", "$");
0818     BatchRenamer::escape(text, "\\%", "%");
0819     BatchRenamer::escape(text, "\\#", "#");
0820     BatchRenamer::escape(text, "\\[", "[");
0821     BatchRenamer::escape(text, "\\]", "]");
0822     // %252f == /, it seems that filenames on unix cannot contain
0823     // a /. So I use %252f, at least konqui displays it correctly
0824     // this was needed, so that plugins that return a slash do not cause errors
0825     BatchRenamer::escape(text, "\\/", "%2f");
0826     BatchRenamer::escape(text, "\\{", "{");
0827     BatchRenamer::escape(text, "\\}", "}");
0828     BatchRenamer::escape(text, "\\*", "*");
0829 
0830     return text;
0831 }
0832 
0833 QString BatchRenamer::processToken(QString token, QString oldname, int i)
0834 {
0835     QString tmp;
0836 
0837     /*
0838      * Call here all functions that handle
0839      * arguments in brackets.
0840      */
0841     tmp = findPartStrings(oldname, token);
0842     if (!tmp.isEmpty()) {
0843         return tmp;
0844     }
0845 
0846     tmp = findDirName(token, (*m_files)[i].srcDirectory());
0847     if (!tmp.isEmpty()) {
0848         return tmp;
0849     }
0850 
0851     tmp = findLength(token, (*m_files)[i].srcFilename());
0852     if (!tmp.isEmpty()) {
0853         return tmp;
0854     }
0855 
0856     tmp = findTrimmed(token, (*m_files)[i].srcFilename(), i);
0857     if (!tmp.isEmpty()) {
0858         return tmp;
0859     }
0860 
0861     tmp = findDirSep(token, (*m_files)[i].srcFilename());
0862     if (!tmp.isEmpty()) {
0863         return tmp;
0864     }
0865 
0866     Plugin *p = PluginLoader::Instance()->findPlugin(token);
0867     if (p) {
0868         tmp = p->processFile(this, i, token, ePluginType_Token);
0869         if (!tmp.isNull()) {
0870             doEscape(tmp);
0871             return tmp;
0872         }
0873     }
0874 
0875     /*
0876      * Maybe I should remove this!
0877      * KRename simply ignores unknown tokens!
0878      * Useful for the MP3 Plugin!
0879      */
0880     return QString();
0881 }
0882 
0883 QString BatchRenamer::findToken(const QString &oldname, QString token, int i)
0884 {
0885     enum conversion { LOWER, UPPER, MIXED, STAR, NONE, EMPTY, NUMBER };
0886     unsigned int numwidth = 0;
0887 
0888     conversion c = EMPTY;
0889     if (!token.left(1).compare("$")) {
0890         c = NONE;
0891     } else if (!token.left(1).compare("%")) {
0892         c = LOWER;
0893     } else if (!token.left(1).compare("&")) {
0894         c = UPPER;
0895     } else if (!token.left(1).compare("")) {
0896         c = MIXED;
0897     } else if (!token.left(1).compare("*")) {
0898         c = STAR;
0899     } else if (!token.left(1).compare("#")) {
0900         while (!token.left(1).compare("#")) {
0901             token.remove(0, 1);
0902             ++numwidth;
0903         }
0904 
0905         c = NUMBER;
0906     }
0907 
0908     if (c != EMPTY && c != NUMBER) {
0909         token.remove(0, 1);
0910     }
0911 
0912     QString save = token;
0913     token = processToken(token, oldname, i);
0914 
0915     switch (c) {
0916     case LOWER:
0917         token = token.toLower();
0918         break;
0919     case UPPER:
0920         token = token.toUpper();
0921         break;
0922     case MIXED:
0923         token = token.toLower();
0924         token.replace(0, 1, token[0].toUpper());
0925         break;
0926     case STAR:
0927         token = capitalize(token);
0928         break;
0929     case NUMBER: {
0930         bool b = false;
0931         int n = token.toInt(&b);
0932         if (b) {
0933             token = token.sprintf("%0*i", numwidth, n);
0934         }
0935     }
0936     break;
0937     default:
0938         break;
0939     }
0940 
0941     return token;
0942 }
0943 
0944 QString BatchRenamer::findPartStrings(QString oldname, QString token)
0945 {
0946     QString first, second;
0947     int pos = -1;
0948 
0949     // MSG: qDebug("PART: %s", token.toUtf8().data() );
0950     // parse things like [2;4{[dirname]}]
0951     if (token.count('{') >= 1 && token.count('}') >= 1) {
0952         int pos = token.indexOf('{');
0953         oldname = token.mid(pos + 1, token.lastIndexOf('}') - pos - 1);
0954         token = token.left(pos);
0955     }
0956 
0957     if (token.contains('-')) {
0958         pos = token.indexOf('-', 0);
0959         first = token.left(pos);
0960         // ------- Code OK ^ !
0961 
0962         second = token.mid(pos + 1, token.length());
0963 
0964         // version < 1.7
0965         // return oldname.mid( first.toInt()-1, second.toInt()-first.toInt() +1 );
0966         // version > 1.7
0967         //return oldname.mid( first.toInt()-1, second.toInt()-first.toInt() );
0968         // version > 1.8
0969 
0970         bool ok;
0971         int sec = second.toInt(&ok);
0972         if (!ok || sec == 0) {
0973             sec = oldname.length();
0974         }
0975 
0976         /*
0977          * x should not be larger than the old name
0978          * and not smaller than zero.
0979          */
0980         int x = sec - first.toInt(&ok);
0981         // if first is no number, but for example length, we return here so that findLength can do its job
0982         if (!ok) {
0983             return QString();
0984         }
0985 
0986         if (x > (signed int)oldname.length() || x < 0) {
0987             x = oldname.length() - first.toInt();
0988         }
0989 
0990         /*
0991          * if I would comment my code I would understand this line :)
0992          * without this line, there is sometimes the last letter
0993          * of a filename missing.
0994          */
0995         if (x != -1) {
0996             x++;
0997         }
0998 
0999         oldname = unEscape(oldname);
1000 
1001         return oldname.mid(first.toInt() - 1, x);
1002     } else if (token.contains(';')) {
1003         pos = token.indexOf(';', 0);
1004 
1005         first = token.left(pos);
1006         second = token.mid(pos + 1, token.length());
1007 
1008         oldname = unEscape(oldname);
1009 
1010         return oldname.mid(first.toInt() - 1, second.toInt());
1011     } else {
1012         bool ok = false;
1013         int number = token.toInt(&ok);
1014 
1015         oldname = unEscape(oldname);
1016 
1017         if (ok && (number <= (signed int)oldname.length() && number > 0)) {
1018             return QString(oldname[ number - 1 ]);
1019         } else {
1020             return QString();
1021         }
1022     }
1023 }
1024 
1025 QString BatchRenamer::findDirName(QString token, QString path)
1026 {
1027     if (token.toLower().startsWith(QLatin1String("dirname"))) {
1028         if (path.right(1) == "/") {
1029             path = path.left(path.length() - 1);
1030         }
1031 
1032         int recursion = 1;
1033         if (token.length() > 7) {
1034             token = token.right(token.length() - 7);
1035             recursion = token.count('.');
1036             if (recursion != (signed int)token.length()) {
1037                 return QString();
1038             }
1039 
1040             recursion++;
1041         }
1042 
1043         return path.section("/", recursion * -1, recursion * -1);
1044     }
1045 
1046     return QString();
1047 }
1048 
1049 QString BatchRenamer::findDirSep(const QString &token, const QString &path)
1050 {
1051     if (token.toLower() == "dirsep") {
1052         return "/";
1053     }
1054 
1055     return QString();
1056 }
1057 
1058 QString BatchRenamer::findLength(const QString &token, const QString &name)
1059 {
1060     if (token.toLower().startsWith(QLatin1String("length"))) {
1061         int minus = 0;
1062         if (token.length() > 6 && token[6] == '-') {
1063             bool n = false;
1064             minus = token.mid(7, token.length() - 7).toInt(&n);
1065             if (!n) {
1066                 minus = 0;
1067             }
1068         }
1069 
1070         QString escaped = name;
1071         escaped = doEscape(escaped);
1072 
1073         return QString::number(escaped.length() - minus);
1074     }
1075 
1076     return QString();
1077 }
1078 
1079 QString BatchRenamer::findTrimmed(const QString &token, const QString &name, int index)
1080 {
1081     if (token.toLower().startsWith(QLatin1String("trimmed"))) {
1082         if (token.contains(';')) {
1083             QString processed = processString(
1084                                     token.section(';', 1, 1), name, index).trimmed();
1085 
1086             if (processed.isNull()) {
1087                 return name.trimmed();
1088             } else {
1089                 return processed.trimmed();
1090             }
1091         } else {
1092             return name.trimmed();
1093         }
1094     }
1095 
1096     return QString();
1097 }
1098 
1099 QString BatchRenamer::findReplace(const QString &text, const QString &origFilename, int index)
1100 {
1101     QList<TReplaceItem>::const_iterator it = m_replace.constBegin();
1102 
1103     QString t(text);
1104     while (it != m_replace.constEnd()) {
1105         QString find((*it).find);
1106 
1107         // Call for each element in replace strings doReplace with correct values
1108         t = doReplace(t, unEscape(find), (*it).replace,
1109                       (*it).reg, (*it).doProcessTokens,
1110                       origFilename, index);
1111         ++it;
1112     }
1113 
1114     return t;
1115 }
1116 
1117 QString BatchRenamer::doReplace(const QString &text, const QString &find, const QString &replace, bool reg, bool doProcessTokens, const QString &origFilename, int index)
1118 {
1119     QString t(text);
1120     if (!reg) {
1121         QString escaped = find;
1122         escaped = doEscape(escaped);
1123 
1124         // we use the escaped text here because the user might want
1125         // to find a "&" and replace it
1126         t.replace(escaped, replace);
1127     } else {
1128         // no doEscape() here for the regexp, because it would destroy our regular expression
1129         // other wise we will not find stuff like $, [ in the text
1130         t = unEscape(t).replace(QRegExp(find), replace);
1131         t = doEscape(t);
1132     }
1133 
1134     if (doProcessTokens) {
1135         t = processString(unEscape(t), origFilename, index, false);
1136     }
1137 
1138     return t;
1139 }
1140 
1141 void BatchRenamer::writeUndoScript(QTextStream *t)
1142 {
1143     // write header comments
1144     (*t) << "#!/bin/sh" << endl
1145          << "# KRename Undo Script" << endl << "#" << endl
1146          << "# KRename was written by:" << endl
1147          << "# Dominik Seichter <domseichter@web.de>" << endl
1148          << "# https://www.krename.net/" << endl << "#" << endl
1149          << "# Script generated by KRename Version: " << VERSION << endl << endl
1150          << "# This script must be started with the option --krename to work!" << endl;
1151 
1152     // write functions:
1153     (*t) << "echo \"KRename Undo Script\"" << endl
1154          << "echo \"https://www.krename.net/\"" << endl
1155          << "echo \"\"" << endl;
1156 
1157     (*t) << "if test --krename = $1 ; then" << endl
1158          << "   echo \"\"" << endl
1159          << "else" << endl
1160          << "   echo \"You have to start this script\"" << endl
1161          << "   echo \"with the command line option\"" << endl
1162          << "   echo \"--krename\"" << endl
1163          << "   echo \"to undo a rename operation.\"" << endl
1164          << "   exit" << endl
1165          << "fi" << endl;
1166 }
1167 
1168 void BatchRenamer::createMissingSubDirs(const QUrl &destUrl, ProgressDialog *dialog)
1169 {
1170     QUrl url = destUrl.adjusted(QUrl::RemoveFilename);
1171     if (url.isEmpty()) {
1172         return;
1173     }
1174 
1175     KIO::MkpathJob *job = KIO::mkpath(url);
1176     KJobWidgets::setWindow(job, dialog);
1177     if (!job->exec()) {
1178         dialog->error(i18n("Cannot create directory %1: %2",
1179                            url.toDisplayString(QUrl::PreferLocalFile),
1180                            job->errorString()));
1181     }
1182 }
1183 
1184 void BatchRenamer::findCounterReset(int i)
1185 {
1186     if ((*m_files)[i - 1].srcDirectory() != (*m_files)[i].srcDirectory())
1187         for (int z = 0; z < (int)m_counters.count(); z++) {
1188             m_counters[z].value = m_counters[z].start - m_counters[z].step;
1189         }
1190 }