/* * This file is part of RawTherapee. * * Copyright (c) 2004-2010 Gabor Horvath * * RawTherapee is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * RawTherapee is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with RawTherapee. If not, see . */ #include #include #include #include #include "../rtengine/rt_math.h" #include "../rtengine/procparams.h" #include #include #include #include #include "cachemanager.h" #include "thumbnail.h" #include "batchqueue.h" #include "batchqueueentry.h" #include "multilangmgr.h" #include "filecatalog.h" #include "batchqueuebuttonset.h" #include "guiutils.h" #include "pathutils.h" #include "rtimage.h" #include using namespace std; using namespace rtengine; #ifdef _WIN32 #define PATH_SEPARATOR '\\'; #else #define PATH_SEPARATOR '/'; #endif namespace // local helper functions { // Look for N or -N in templateText at position ix, meaning "index from end" and "index from start" // For N, return Nth index from the end, and for -N return the Nth index from the start // N is a digit 1 through 9 unsigned int decodePathIndex(unsigned int & ix, Glib::ustring & templateText, size_t numPathElements) { unsigned int pathIndex = numPathElements; // means input was invalid bool fromStart = false; if (ix < templateText.size()) { if (templateText[ix] == '-') { fromStart = true; // minus sign means N is from the start rather than the end of the path ix++; } } if (ix < templateText.size()) { unsigned int n = templateText[ix] - '1'; if (!fromStart) { n = numPathElements - n - 1; } if (n < numPathElements) { pathIndex = n; } } return pathIndex; } } BatchQueue::BatchQueue (FileCatalog* aFileCatalog) : processing(nullptr), fileCatalog(aFileCatalog), sequence(0), listener(nullptr) { location = THLOC_BATCHQUEUE; int p = 0; pmenu.attach (*Gtk::manage(open = new Gtk::MenuItem (M("FILEBROWSER_POPUPOPENINEDITOR"))), 0, 1, p, p + 1); p++; pmenu.attach (*Gtk::manage(selall = new Gtk::MenuItem (M("FILEBROWSER_POPUPSELECTALL"))), 0, 1, p, p + 1); p++; pmenu.attach (*Gtk::manage(new Gtk::SeparatorMenuItem ()), 0, 1, p, p + 1); p++; pmenu.attach (*Gtk::manage(head = new MyImageMenuItem (M("FILEBROWSER_POPUPMOVEHEAD"), "goto-start-small.png")), 0, 1, p, p + 1); p++; pmenu.attach (*Gtk::manage(tail = new MyImageMenuItem (M("FILEBROWSER_POPUPMOVEEND"), "goto-end-small.png")), 0, 1, p, p + 1); p++; pmenu.attach (*Gtk::manage(new Gtk::SeparatorMenuItem ()), 0, 1, p, p + 1); p++; pmenu.attach (*Gtk::manage(cancel = new MyImageMenuItem (M("FILEBROWSER_POPUPCANCELJOB"), "cancel-small.png")), 0, 1, p, p + 1); pmenu.show_all (); // Accelerators pmaccelgroup = Gtk::AccelGroup::create (); pmenu.set_accel_group (pmaccelgroup); open->add_accelerator ("activate", pmaccelgroup, GDK_KEY_e, Gdk::CONTROL_MASK, Gtk::ACCEL_VISIBLE); selall->add_accelerator ("activate", pmaccelgroup, GDK_KEY_a, Gdk::CONTROL_MASK, Gtk::ACCEL_VISIBLE); head->add_accelerator ("activate", pmaccelgroup, GDK_KEY_Home, (Gdk::ModifierType)0, Gtk::ACCEL_VISIBLE); tail->add_accelerator ("activate", pmaccelgroup, GDK_KEY_End, (Gdk::ModifierType)0, Gtk::ACCEL_VISIBLE); cancel->add_accelerator ("activate", pmaccelgroup, GDK_KEY_Delete, (Gdk::ModifierType)0, Gtk::ACCEL_VISIBLE); open->signal_activate().connect(sigc::mem_fun(*this, &BatchQueue::openLastSelectedItemInEditor)); cancel->signal_activate().connect (std::bind (&BatchQueue::cancelItems, this, std::ref (selected))); head->signal_activate().connect (std::bind (&BatchQueue::headItems, this, std::ref (selected))); tail->signal_activate().connect (std::bind (&BatchQueue::tailItems, this, std::ref (selected))); selall->signal_activate().connect (sigc::mem_fun(*this, &BatchQueue::selectAll)); setArrangement (ThumbBrowserBase::TB_Vertical); } BatchQueue::~BatchQueue () { std::set removable_bqes; mutex_removable_batch_queue_entries.lock(); removable_batch_queue_entries.swap(removable_bqes); mutex_removable_batch_queue_entries.unlock(); for (const auto entry : removable_bqes) { ::g_remove(entry->savedParamsFile.c_str()); delete entry; } idle_register.destroy(); MYWRITERLOCK(l, entryRW); // The listener merges parameters with old values, so delete afterwards for (size_t i = 0; i < fd.size(); i++) { delete fd.at(i); } fd.clear (); } void BatchQueue::resizeLoadedQueue() { MYWRITERLOCK(l, entryRW); const auto height = getThumbnailHeight (); for (const auto entry : fd) entry->resize(height); } // Reduce the max size of a thumb, since thumb is processed synchronously on adding to queue // leading to very long waiting when adding more images int BatchQueue::calcMaxThumbnailHeight() { return std::min(options.maxThumbnailHeight, 200); } // Function for virtual override in thumbbrowser base int BatchQueue::getMaxThumbnailHeight() const { return calcMaxThumbnailHeight(); } void BatchQueue::saveThumbnailHeight (int height) { options.thumbSizeQueue = height; } int BatchQueue::getThumbnailHeight () { // The user could have manually forced the option to a too big value return std::max(std::min(options.thumbSizeQueue, 200), 10); } void BatchQueue::rightClicked () { pmenu.popup (3, this->eventTime); } void BatchQueue::doubleClicked(ThumbBrowserEntryBase* entry) { openItemInEditor(entry); } bool BatchQueue::keyPressed (GdkEventKey* event) { bool ctrl = event->state & GDK_CONTROL_MASK; if ((event->keyval == GDK_KEY_A || event->keyval == GDK_KEY_a) && ctrl) { selectAll (); return true; } else if ((event->keyval == GDK_KEY_E || event->keyval == GDK_KEY_e) && ctrl) { openLastSelectedItemInEditor(); return true; } else if (event->keyval == GDK_KEY_Home) { headItems (selected); return true; } else if (event->keyval == GDK_KEY_End) { tailItems (selected); return true; } else if (event->keyval == GDK_KEY_Delete) { cancelItems (selected); return true; } return false; } void BatchQueue::addEntries (const std::vector& entries, bool head, bool save) { { MYWRITERLOCK(l, entryRW); for (const auto entry : entries) { entry->setParent (this); // BatchQueueButtonSet have to be added before resizing to take them into account const auto bqbs = new BatchQueueButtonSet (entry); bqbs->setButtonListener (this); entry->addButtonSet (bqbs); // batch queue might have smaller, restricted size entry->resize (getThumbnailHeight()); // recovery save const auto tempFile = getTempFilenameForParams (entry->filename); if (!entry->params->save (tempFile)) entry->savedParamsFile = tempFile; entry->selected = false; // insert either at the end, or before the first non-processing entry auto pos = fd.end (); if (head) pos = std::find_if (fd.begin (), fd.end (), [] (const ThumbBrowserEntryBase* fdEntry) { return !fdEntry->processing; }); fd.insert (pos, entry); if (entry->thumbnail) entry->thumbnail->imageEnqueued (); } } if (save) saveBatchQueue (); redraw (); notifyListener (); } bool BatchQueue::saveBatchQueue () { const auto fileName = Glib::build_filename (options.rtdir, "batch", "queue.csv"); std::ofstream file (fileName, std::ios::binary | std::ios::trunc); if (!file.is_open ()) return false; { MYREADERLOCK(l, entryRW); if (fd.empty ()) return true; // The column's header is mandatory (the first line will be skipped when loaded) file << "input image full path|param file full path|output image full path|file format|jpeg quality|jpeg subsampling|" << "png bit depth|png compression|tiff bit depth|tiff is float|uncompressed tiff|save output params|force format options|fast export|" << std::endl; // method is already running with entryLock, so no need to lock again for (const auto fdEntry : fd) { const auto entry = static_cast (fdEntry); const auto& saveFormat = entry->saveFormat; // Warning: for code's simplicity in loadBatchQueue, each field must end by the '|' character, safer than ';' or ',' since it can't be used in paths #ifdef _WIN32 // on windows it crashes if we don't use c_str() and filename etc. contain special (e.g. chinese) characters, see issue 3387 file << entry->filename.c_str() << '|' << entry->savedParamsFile.c_str() << '|' << entry->outFileName.c_str() << '|' << saveFormat.format << '|' #else file << entry->filename << '|' << entry->savedParamsFile << '|' << entry->outFileName << '|' << saveFormat.format << '|' #endif << saveFormat.jpegQuality << '|' << saveFormat.jpegSubSamp << '|' << saveFormat.pngBits << '|' << saveFormat.tiffBits << '|' << (saveFormat.tiffFloat ? 1 : 0) << '|' << saveFormat.tiffUncompressed << '|' << saveFormat.saveParams << '|' << entry->forceFormatOpts << '|' << entry->fast_pipeline << '|' << saveFormat.bigTiff << '|' << std::endl; } } return true; } bool BatchQueue::loadBatchQueue () { const auto fileName = Glib::build_filename (options.rtdir, "batch", "queue.csv"); std::ifstream file (fileName, std::ios::binary); if (file.is_open ()) { // Yes, it's better to get the lock for the whole file reading, // to update the list in one shot without any other concurrent access! MYWRITERLOCK(l, entryRW); std::string row, column; std::vector values; // skipping the first row std::getline (file, row); while (std::getline (file, row)) { std::istringstream line (row); values.clear (); while (std::getline(line, column, '|')) { values.push_back (column); } auto value = values.begin (); const auto nextStringOr = [&] (const Glib::ustring& defaultValue) -> Glib::ustring { return value != values.end () ? Glib::ustring(*value++) : defaultValue; }; const auto nextIntOr = [&] (int defaultValue) -> int { try { return value != values.end () ? std::stoi(*value++) : defaultValue; } catch (std::exception&) { return defaultValue; } }; const auto source = nextStringOr (Glib::ustring ()); const auto paramsFile = nextStringOr (Glib::ustring ()); if (source.empty () || paramsFile.empty ()) continue; const auto outputFile = nextStringOr (Glib::ustring ()); const auto saveFmt = nextStringOr (options.saveFormat.format); const auto jpegQuality = nextIntOr (options.saveFormat.jpegQuality); const auto jpegSubSamp = nextIntOr (options.saveFormat.jpegSubSamp); const auto pngBits = nextIntOr (options.saveFormat.pngBits); const auto tiffBits = nextIntOr (options.saveFormat.tiffBits); const auto tiffFloat = nextIntOr (options.saveFormat.tiffFloat); const auto tiffUncompressed = nextIntOr (options.saveFormat.tiffUncompressed); const auto saveParams = nextIntOr (options.saveFormat.saveParams); const auto forceFormatOpts = nextIntOr (options.forceFormatOpts); const auto fast = nextIntOr(false); const auto bigTiff = nextIntOr (options.saveFormat.bigTiff); rtengine::procparams::ProcParams pparams; if (pparams.load (paramsFile)) continue; auto thumb = CacheManager::getInstance ()->getEntry (source); if (!thumb) continue; auto job = rtengine::ProcessingJob::create (source, thumb->getType () == FT_Raw, pparams, fast); auto prevh = getMaxThumbnailHeight(); auto prevw = prevh; thumb->getThumbnailSize(prevw, prevh, &pparams); auto entry = new BatchQueueEntry (job, pparams, source, prevw, prevh, thumb, options.overwriteOutputFile); thumb->decreaseRef (); // Removing the refCount acquired by cacheMgr->getEntry entry->setParent (this); // BatchQueueButtonSet have to be added before resizing to take them into account auto bqbs = new BatchQueueButtonSet (entry); bqbs->setButtonListener (this); entry->addButtonSet (bqbs); entry->savedParamsFile = paramsFile; entry->selected = false; entry->outFileName = outputFile; if (!outputFile.empty ()) { auto& saveFormat = entry->saveFormat; saveFormat.format = saveFmt; saveFormat.jpegQuality = jpegQuality; saveFormat.jpegSubSamp = jpegSubSamp; saveFormat.pngBits = pngBits; saveFormat.tiffBits = tiffBits; saveFormat.tiffFloat = tiffFloat == 1; saveFormat.tiffUncompressed = tiffUncompressed != 0; saveFormat.bigTiff = bigTiff != 0; saveFormat.saveParams = saveParams != 0; entry->forceFormatOpts = forceFormatOpts != 0; } else { entry->forceFormatOpts = false; } fd.push_back (entry); } } redraw (); notifyListener (); return !fd.empty (); } Glib::ustring BatchQueue::getTempFilenameForParams( const Glib::ustring &filename ) { timeval tv; gettimeofday(&tv, nullptr); char mseconds[11]; snprintf(mseconds, sizeof(mseconds), "%d", (int)(tv.tv_usec / 1000)); time_t rawtime; struct tm *timeinfo; char stringTimestamp [80]; time ( &rawtime ); timeinfo = localtime ( &rawtime ); strftime (stringTimestamp, sizeof(stringTimestamp), "_%Y%m%d%H%M%S_", timeinfo); Glib::ustring savedParamPath; savedParamPath = options.rtdir + "/batch/"; g_mkdir_with_parents (savedParamPath.c_str (), 0755); savedParamPath += Glib::path_get_basename (filename); savedParamPath += stringTimestamp; savedParamPath += mseconds; savedParamPath += paramFileExtension; return savedParamPath; } void BatchQueue::cancelItems (const std::vector& items) { std::set removable_bqes; { MYWRITERLOCK(l, entryRW); for (const auto item : items) { const auto entry = static_cast (item); if (entry->processing) continue; const auto pos = std::find (fd.begin (), fd.end (), entry); if (pos == fd.end ()) continue; fd.erase (pos); rtengine::ProcessingJob::destroy (entry->job); if (entry->thumbnail) entry->thumbnail->imageRemovedFromQueue (); removable_bqes.insert(entry); } for (const auto entry : fd) entry->selected = false; lastClicked = nullptr; selected.clear (); } if (!removable_bqes.empty()) { mutex_removable_batch_queue_entries.lock(); removable_batch_queue_entries.insert(removable_bqes.begin(), removable_bqes.end()); mutex_removable_batch_queue_entries.unlock(); idle_register.add( [this]() -> bool { std::set removable_bqes; mutex_removable_batch_queue_entries.lock(); removable_batch_queue_entries.swap(removable_bqes); mutex_removable_batch_queue_entries.unlock(); for (const auto entry : removable_bqes) { ::g_remove(entry->savedParamsFile.c_str()); delete entry; } return false; } ); } saveBatchQueue (); redraw (); notifyListener (); } void BatchQueue::headItems (const std::vector& items) { { MYWRITERLOCK(l, entryRW); for (auto item = items.rbegin(); item != items.rend(); ++item) { const auto entry = static_cast (*item); if (entry->processing) continue; const auto pos = std::find (fd.begin (), fd.end (), entry); if (pos == fd.end () || pos == fd.begin ()) continue; fd.erase (pos); // find the first item that is not under processing const auto newPos = std::find_if (fd.begin (), fd.end (), [] (const ThumbBrowserEntryBase* fdEntry) { return !fdEntry->processing; }); fd.insert (newPos, entry); } } saveBatchQueue (); redraw (); } void BatchQueue::tailItems (const std::vector& items) { { MYWRITERLOCK(l, entryRW); for (const auto item : items) { const auto entry = static_cast (item); if (entry->processing) continue; const auto pos = std::find (fd.begin (), fd.end (), entry); if (pos == fd.end ()) continue; fd.erase (pos); fd.push_back (entry); } } saveBatchQueue (); redraw (); } void BatchQueue::selectAll () { { MYWRITERLOCK(l, entryRW); lastClicked = nullptr; selected.clear (); for (size_t i = 0; i < fd.size(); i++) { if (fd[i]->processing) { continue; } fd[i]->selected = true; selected.push_back (fd[i]); } } queue_draw (); } void BatchQueue::openLastSelectedItemInEditor() { { MYREADERLOCK(l, entryRW); if (!selected.empty()) { openItemInEditor(selected.back()); } } } void BatchQueue::updateDestinationPathPreview() { MYWRITERLOCK(l, entryRW); if (!selected.empty()) { auto &entry = *selected.at(0); int sequence = 0; // Sequence during subsequent queue processing can't be determined here Glib::ustring baseDestination = calcAutoFileNameBase(entry.filename, sequence); Glib::ustring destination = Glib::ustring::compose ("%1.%2", baseDestination, options.saveFormatBatch.format); if (listener) { listener->setDestinationPreviewText(destination); } } } void BatchQueue::openItemInEditor(ThumbBrowserEntryBase* item) { if (item) { std::vector< ::Thumbnail*> requestedItem; requestedItem.push_back(item->thumbnail); fileCatalog->openRequested(requestedItem); } } void BatchQueue::startProcessing () { if (!processing) { MYWRITERLOCK(l, entryRW); if (!fd.empty()) { BatchQueueEntry* next; next = static_cast(fd[0]); // tag it as processing and set sequence next->processing = true; next->sequence = sequence = 1; processing = next; // remove from selection if (processing->selected) { std::vector::iterator pos = std::find (selected.begin(), selected.end(), processing); if (pos != selected.end()) { selected.erase (pos); } processing->selected = false; } MYWRITERLOCK_RELEASE(l); // remove button set next->removeButtonSet (); // start batch processing rtengine::startBatchProcessing (next->job, this); queue_draw (); notifyListener(); } } } void BatchQueue::setProgress(double p) { if (processing) { processing->progress = p; } // No need to acquire the GUI, setProgressUI will do it idle_register.add( [this]() -> bool { redraw(); return false; } ); } void BatchQueue::setProgressStr(const Glib::ustring& str) { } void BatchQueue::setProgressState(bool inProcessing) { } void BatchQueue::error(const Glib::ustring& descr) { if (processing && processing->processing) { // restore failed thumb BatchQueueButtonSet* bqbs = new BatchQueueButtonSet (processing); bqbs->setButtonListener (this); processing->addButtonSet (bqbs); processing->processing = false; processing->job = rtengine::ProcessingJob::create(processing->filename, processing->thumbnail->getType() == FT_Raw, *processing->params); processing = nullptr; redraw (); } if (listener) { BatchQueueListener* const bql = listener; idle_register.add( [bql, descr]() -> bool { bql->queueSizeChanged(0, false, true, descr); return false; } ); } } rtengine::ProcessingJob* BatchQueue::imageReady(rtengine::IImagefloat* img) { // save image img Glib::ustring fname; SaveFormat saveFormat; if (processing->outFileName.empty()) { // auto file name Glib::ustring s = calcAutoFileNameBase (processing->filename, processing->sequence); saveFormat = options.saveFormatBatch; fname = autoCompleteFileName (s, saveFormat.format); } else { // use the save-as filename with automatic completion for uniqueness if (processing->forceFormatOpts) { saveFormat = processing->saveFormat; } else { saveFormat = options.saveFormatBatch; } // The output filename's extension is forced to the current or selected output format, // despite what the user have set in the filename's field of the "Save as" dialog box fname = autoCompleteFileName (removeExtension(processing->outFileName), saveFormat.format); //fname = autoCompleteFileName (removeExtension(processing->outFileName), getExtension(processing->outFileName)); } //printf ("fname=%s, %s\n", fname.c_str(), removeExtension(fname).c_str()); if (img && !fname.empty()) { int err = 0; if (saveFormat.format == "tif") { err = img->saveAsTIFF ( fname, saveFormat.tiffBits, saveFormat.tiffFloat, saveFormat.tiffUncompressed, saveFormat.bigTiff ); } else if (saveFormat.format == "png") { err = img->saveAsPNG (fname, saveFormat.pngBits); } else if (saveFormat.format == "jpg") { err = img->saveAsJPEG (fname, saveFormat.jpegQuality, saveFormat.jpegSubSamp); } delete img; if (err) { throw Glib::FileError(Glib::FileError::FAILED, M("MAIN_MSG_CANNOTSAVE") + "\n" + fname); } if (saveFormat.saveParams) { // We keep the extension to avoid overwriting the profile when we have // the same output filename with different extension //processing->params.save (removeExtension(fname) + paramFileExtension); processing->params->save (fname + ".out" + paramFileExtension); } if (processing->thumbnail) { processing->thumbnail->imageDeveloped (); processing->thumbnail->imageRemovedFromQueue (); } } // save temporary params file name: delete as last thing Glib::ustring processedParams = processing->savedParamsFile; // delete from the queue bool remove_button_set = false; { MYWRITERLOCK(l, entryRW); delete processing; processing = nullptr; fd.erase (fd.begin()); // return next job if (!fd.empty() && listener && listener->canStartNext ()) { BatchQueueEntry* next = static_cast(fd[0]); // tag it as selected and set sequence next->processing = true; next->sequence = ++sequence; processing = next; // remove from selection if (processing->selected) { std::vector::iterator pos = std::find (selected.begin(), selected.end(), processing); if (pos != selected.end()) { selected.erase (pos); } processing->selected = false; } // remove button set remove_button_set = true; } } if (remove_button_set) { // ButtonSet have Cairo::Surface which might be rendered while we're trying to delete them GThreadLock lock; processing->removeButtonSet (); } if (saveBatchQueue ()) { ::g_remove (processedParams.c_str ()); // Delete all files in directory batch when finished, just to be sure to remove zombies auto isEmpty = false; { MYREADERLOCK(l, entryRW); isEmpty = fd.empty(); } if (isEmpty) { const auto batchdir = Glib::build_filename (options.rtdir, "batch"); try { auto dir = Gio::File::create_for_path (batchdir); auto enumerator = dir->enumerate_children ("standard::name"); while (auto file = enumerator->next_file ()) { ::g_remove (Glib::build_filename (batchdir, file->get_name ()).c_str ()); } } catch (Glib::Exception&) {} } } redraw (); notifyListener (); return processing ? processing->job : nullptr; } // Calculates automatic filename of processed batch entry, but just the base name // example output: "c:\out\converted\dsc0121" Glib::ustring BatchQueue::calcAutoFileNameBase (const Glib::ustring& origFileName, int sequence) { std::vector da; for (size_t i = 0; i < origFileName.size(); i++) { while ((i < origFileName.size()) && (origFileName[i] == '\\' || origFileName[i] == '/')) { i++; } if (i >= origFileName.size()) { break; } Glib::ustring tok; while ((i < origFileName.size()) && !(origFileName[i] == '\\' || origFileName[i] == '/')) { tok = tok + origFileName[i++]; } if (i < origFileName.size()) { // omit the last token, which is the file name da.push_back (tok); } } // for (int i=0; i= 0 && origFileName[extpos] != '.'; extpos--); for (int k = extpos - 1; k >= 0 && origFileName[k] != '/' && origFileName[k] != '\\'; k--) { filename = origFileName[k] + filename; } // printf ("%d, |%s|\n", extpos, filename.c_str()); // constructing full output path // printf ("path=|%s|\n", options.savePath.c_str()); Glib::ustring path; if (options.saveUsePathTemplate) { unsigned int ix = 0; while (ix < options.savePathTemplate.size()) { if (options.savePathTemplate[ix] == '%') { ix++; if (options.savePathTemplate[ix] == 'P') { // insert path elements from given index to the end ix++; unsigned int n = decodePathIndex(ix, options.savePathTemplate, da.size()); if (n < da.size()) { for (unsigned int i=n; i < da.size(); i++) { path += da[i] + PATH_SEPARATOR; } } // If the next template character is a separator, skip it, because path already has one ix++; if (ix < options.savePathTemplate.size() && options.savePathTemplate[ix] != '/' && options.savePathTemplate[ix] != '\\') { ix--; } } else if (options.savePathTemplate[ix] == 'p') { // insert path elements from the start of the path up to the given index ix++; unsigned int n = decodePathIndex(ix, options.savePathTemplate, da.size()); for (unsigned int i=0; i <= n && i < da.size(); i++) { path += da[i] + PATH_SEPARATOR; } // If the next template character is a separator, skip it, because path already has one ix++; if (ix < options.savePathTemplate.size() && options.savePathTemplate[ix] != '/' && options.savePathTemplate[ix] != '\\') { ix--; } } else if (options.savePathTemplate[ix] == 'd') { // insert a single directory name from the file's path // da.size()-1 omits the last element, which is the filename ix++; unsigned int n = decodePathIndex(ix, options.savePathTemplate, da.size()); if (n < da.size()) { path += da[n]; } } else if (options.savePathTemplate[ix] == 'f') { path += filename; } else if (options.savePathTemplate[ix] == 'r') { // rank from pparams char rank; rtengine::procparams::ProcParams pparams; if( pparams.load(origFileName + paramFileExtension) == 0 ) { if (!pparams.inTrash) { rank = pparams.rank + '0'; } else { rank = 'x'; } } else { rank = '0'; // if param file not loaded (e.g. does not exist), default to rank=0 } path += rank; } else if (options.savePathTemplate[ix] == 's') { // sequence std::ostringstream seqstr; int w = options.savePathTemplate[ix + 1] - '0'; if (w >= 1 && w <= 9) { ix++; seqstr << std::setw (w) << std::setfill ('0'); } seqstr << sequence; path += seqstr.str (); } } else { path += options.savePathTemplate[ix]; } ix++; } } else { path = Glib::build_filename (options.savePathFolder, filename); } return path; } Glib::ustring BatchQueue::autoCompleteFileName (const Glib::ustring& fileName, const Glib::ustring& format) { // separate filename and the path to the destination directory Glib::ustring dstdir = Glib::path_get_dirname (fileName); Glib::ustring dstfname = Glib::path_get_basename (fileName); Glib::ustring fname; // create directory, if does not exist if (g_mkdir_with_parents (dstdir.c_str (), 0755)) { return Glib::ustring (); } // In overwrite mode we TRY to delete the old file first. // if that's not possible (e.g. locked by viewer, R/O), we revert to the standard naming scheme bool inOverwriteMode = processing->overwriteFile; for (int tries = 0; tries < 100; tries++) { if (tries == 0) { fname = Glib::ustring::compose ("%1.%2", Glib::build_filename (dstdir, dstfname), format); } else { fname = Glib::ustring::compose ("%1-%2.%3", Glib::build_filename (dstdir, dstfname), tries, format); } int fileExists = Glib::file_test (fname, Glib::FILE_TEST_EXISTS); if (inOverwriteMode && fileExists) { if (::g_remove (fname.c_str ()) != 0) { inOverwriteMode = false; // failed to delete- revert to old naming scheme } else { fileExists = false; // deleted now } } if (!fileExists) { return fname; } } return ""; } void BatchQueue::buttonPressed (LWButton* button, int actionCode, void* actionData) { const std::vector bqe = {static_cast(actionData)}; if (actionCode == 10) { // cancel cancelItems (bqe); } else if (actionCode == 8) { // to head headItems (bqe); } else if (actionCode == 9) { // to tail tailItems (bqe); } } void BatchQueue::notifyListener () { const bool queueRunning = processing; if (listener) { BatchQueueListener* const bql = listener; int qsize = 0; { MYREADERLOCK(l, entryRW); qsize = fd.size(); } idle_register.add( [bql, qsize, queueRunning]() -> bool { bql->queueSizeChanged(qsize, queueRunning, false, {}); return false; } ); } } void BatchQueue::redrawNeeded (LWButton* button) { GThreadLock lock; queue_draw (); } void BatchQueue::selectionChanged() { updateDestinationPathPreview(); }