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