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 }