/** * Copyright (C) 2004, 2007, 2009 Michael Pyne * Copyright (C) 2003 Frerich Raabe * Copyright (C) 2014 Arnold Dumas * * This program 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 2 of the License, or (at your option) any later * version. * * This program 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 * this program. If not, see . */ #include "filerenamer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tag.h" #include "filerenameroptions.h" #include "filehandle.h" #include "exampleoptions.h" #include "playlistitem.h" #include "playlist.h" // processEvents() #include "coverinfo.h" class ConfirmationDialog : public KDialog { public: ConfirmationDialog(const QMap &files, QWidget *parent = 0, const char *name = 0) : KDialog(parent) { setObjectName( QLatin1String( name ) ); setModal(true); setCaption(i18nc("warning about mass file rename", "Warning")); setButtons(Ok | Cancel); KVBox *vbox = new KVBox(this); setMainWidget(vbox); KVBox *hbox = new KVBox(vbox); QLabel *l = new QLabel(hbox); l->setPixmap(SmallIcon("dialog-warning", 32)); l = new QLabel(i18n("You are about to rename the following files. " "Are you sure you want to continue?"), hbox); hbox->setStretchFactor(l, 1); QTreeWidget *lv = new QTreeWidget(vbox); QStringList headers; headers << i18n("Original Name"); headers << i18n("New Name"); lv->setHeaderLabels(headers); lv->setRootIsDecorated(false); int lvHeight = 0; QMap::ConstIterator it = files.constBegin(); for(; it != files.constEnd(); ++it) { QTreeWidgetItem *item = new QTreeWidgetItem(lv); item->setText(0, it.key()); if (it.key() != it.value()) { item->setText(1, it.value()); } else { item->setText(1, i18n("No Change")); } lvHeight += lv->visualItemRect(item).height(); } lvHeight += lv->horizontalScrollBar()->height() + lv->header()->height(); lv->setMinimumHeight(qMin(lvHeight, 400)); resize(qMin(width(), 500), qMin(minimumHeight(), 400)); show(); } }; // // Implementation of ConfigCategoryReader // ConfigCategoryReader::ConfigCategoryReader() : CategoryReaderInterface(), m_currentItem(0) { KConfigGroup config(KGlobal::config(), "FileRenamer"); QList categoryOrder = config.readEntry("CategoryOrder", QList()); int categoryCount[NumTypes] = { 0 }; // Keep track of each category encountered. // Set a default: if(categoryOrder.isEmpty()) categoryOrder << Artist << Album << Title << Track; QList::ConstIterator catIt = categoryOrder.constBegin(); for(; catIt != categoryOrder.constEnd(); ++catIt) { int catCount = categoryCount[*catIt]++; TagType category = static_cast(*catIt); CategoryID catId(category, catCount); m_options[catId] = TagRenamerOptions(catId); m_categoryOrder << catId; } m_folderSeparators.fill(false, m_categoryOrder.count() - 1); QList checkedSeparators = config.readEntry("CheckedDirSeparators", QList()); QList::ConstIterator it = checkedSeparators.constBegin(); for(; it != checkedSeparators.constEnd(); ++it) { if(*it < m_folderSeparators.count()) m_folderSeparators[*it] = true; } m_musicFolder = config.readPathEntry("MusicFolder", "${HOME}/music"); m_separator = config.readEntry("Separator", " - "); } QString ConfigCategoryReader::categoryValue(TagType type) const { if(!m_currentItem) return QString(); Tag *tag = m_currentItem->file().tag(); switch(type) { case Track: return QString::number(tag->track()); case Year: return QString::number(tag->year()); case Title: return tag->title(); case Artist: return tag->artist(); case Album: return tag->album(); case Genre: return tag->genre(); default: return QString(); } } QString ConfigCategoryReader::prefix(const CategoryID &category) const { return m_options[category].prefix(); } QString ConfigCategoryReader::suffix(const CategoryID &category) const { return m_options[category].suffix(); } TagRenamerOptions::EmptyActions ConfigCategoryReader::emptyAction(const CategoryID &category) const { return m_options[category].emptyAction(); } QString ConfigCategoryReader::emptyText(const CategoryID &category) const { return m_options[category].emptyText(); } QList ConfigCategoryReader::categoryOrder() const { return m_categoryOrder; } QString ConfigCategoryReader::separator() const { return m_separator; } QString ConfigCategoryReader::musicFolder() const { return m_musicFolder; } int ConfigCategoryReader::trackWidth(int categoryNum) const { return m_options[CategoryID(Track, categoryNum)].trackWidth(); } bool ConfigCategoryReader::hasFolderSeparator(int index) const { if(index >= m_folderSeparators.count()) return false; return m_folderSeparators[index]; } bool ConfigCategoryReader::isDisabled(const CategoryID &category) const { return m_options[category].disabled(); } // // Implementation of FileRenamerWidget // FileRenamerWidget::FileRenamerWidget(QWidget *parent) : QWidget(parent), CategoryReaderInterface(), m_ui(new Ui::FileRenamerBase), m_exampleFromFile(false) { m_ui->setupUi(this); // This must be created before createTagRows() is called. m_exampleDialog = new ExampleOptionsDialog(this); createTagRows(); loadConfig(); // Add correct text to combo box. m_ui->m_category->clear(); for(int i = StartTag; i < NumTypes; ++i) { QString category = TagRenamerOptions::tagTypeText(static_cast(i)); m_ui->m_category->addItem(category); } connect(m_exampleDialog, SIGNAL(signalShown()), SLOT(exampleDialogShown())); connect(m_exampleDialog, SIGNAL(signalHidden()), SLOT(exampleDialogHidden())); connect(m_exampleDialog, SIGNAL(dataChanged()), SLOT(dataSelected())); connect(m_exampleDialog, SIGNAL(fileChanged(QString)), this, SLOT(fileSelected(QString))); exampleTextChanged(); } void FileRenamerWidget::loadConfig() { QList checkedSeparators; KConfigGroup config(KGlobal::config(), "FileRenamer"); for(int i = 0; i < m_rows.count(); ++i) m_rows[i].options = TagRenamerOptions(m_rows[i].category); checkedSeparators = config.readEntry("CheckedDirSeparators", QList()); foreach(int separator, checkedSeparators) { if(separator < m_folderSwitches.count()) m_folderSwitches[separator]->setChecked(true); } QString path = config.readEntry("MusicFolder", "${HOME}/music"); m_ui->m_musicFolder->setUrl(KUrl(path)); m_ui->m_musicFolder->setMode(KFile::Directory | KFile::ExistingOnly | KFile::LocalOnly); m_ui->m_separator->setEditText(config.readEntry("Separator", " - ")); } void FileRenamerWidget::saveConfig() { KConfigGroup config(KGlobal::config(), "FileRenamer"); QList checkedSeparators; QList categoryOrder; for(int i = 0; i < m_rows.count(); ++i) { int rowId = idOfPosition(i); // Write out in GUI order, not m_rows order m_rows[rowId].options.saveConfig(m_rows[rowId].category.categoryNumber); categoryOrder += m_rows[rowId].category.category; } for(int i = 0; i < m_folderSwitches.count(); ++i) if(m_folderSwitches[i]->isChecked() == true) checkedSeparators += i; config.writeEntry("CheckedDirSeparators", checkedSeparators); config.writeEntry("CategoryOrder", categoryOrder); config.writePathEntry("MusicFolder", m_ui->m_musicFolder->url().path()); config.writeEntry("Separator", m_ui->m_separator->currentText()); config.sync(); } FileRenamerWidget::~FileRenamerWidget() { } int FileRenamerWidget::addRowCategory(TagType category) { static QIcon up = SmallIcon("go-up"); static QIcon down = SmallIcon("go-down"); // Find number of categories already of this type. int categoryCount = 0; for(int i = 0; i < m_rows.count(); ++i) if(m_rows[i].category.category == category) ++categoryCount; Row row; row.category = CategoryID(category, categoryCount); row.position = m_rows.count(); int id = row.position; QFrame *frame = new QFrame(m_mainFrame); QHBoxLayout *frameLayout = new QHBoxLayout(frame); frameLayout->setMargin(3); row.widget = frame; frame->setFrameShape(QFrame::Box); frame->setLineWidth(1); QBoxLayout *mainFrameLayout = static_cast(m_mainFrame->layout()); mainFrameLayout->addWidget(frame, 1); QFrame *buttons = new QFrame(frame); QVBoxLayout *buttonLayout = new QVBoxLayout(buttons); frameLayout->addWidget(buttons); buttons->setFrameStyle(QFrame::Plain | QFrame::Box); buttons->setLineWidth(1); row.upButton = new KPushButton(buttons); row.downButton = new KPushButton(buttons); row.upButton->setIcon(up); row.downButton->setIcon(down); row.upButton->setFlat(true); row.downButton->setFlat(true); upMapper->connect(row.upButton, SIGNAL(clicked()), SLOT(map())); upMapper->setMapping(row.upButton, id); downMapper->connect(row.downButton, SIGNAL(clicked()), SLOT(map())); downMapper->setMapping(row.downButton, id); buttonLayout->addWidget(row.upButton); buttonLayout->addWidget(row.downButton); QString labelText = QString("%1").arg(TagRenamerOptions::tagTypeText(category)); QLabel *label = new QLabel(labelText, frame); frameLayout->addWidget(label, 1); label->setAlignment(Qt::AlignCenter); QVBoxLayout *optionLayout = new QVBoxLayout; frameLayout->addLayout(optionLayout); row.enableButton = new KPushButton(i18nc("remove music genre from file renamer", "Remove"), frame); optionLayout->addWidget(row.enableButton); toggleMapper->connect(row.enableButton, SIGNAL(clicked()), SLOT(map())); toggleMapper->setMapping(row.enableButton, id); row.optionsButton = new KPushButton(i18nc("file renamer genre options", "Options"), frame); optionLayout->addWidget(row.optionsButton); mapper->connect(row.optionsButton, SIGNAL(clicked()), SLOT(map())); mapper->setMapping(row.optionsButton, id); row.widget->show(); m_rows.append(row); // Disable add button if there's too many rows. if(m_rows.count() == MAX_CATEGORIES) m_ui->m_insertCategory->setEnabled(false); return id; } void FileRenamerWidget::moveSignalMappings(int oldId, int newId) { mapper->setMapping(m_rows[oldId].optionsButton, newId); downMapper->setMapping(m_rows[oldId].downButton, newId); upMapper->setMapping(m_rows[oldId].upButton, newId); toggleMapper->setMapping(m_rows[oldId].enableButton, newId); } bool FileRenamerWidget::removeRow(int id) { if(id >= m_rows.count()) { kWarning() << "Trying to remove row, but " << id << " is out-of-range.\n"; return false; } if(m_rows.count() == 1) { kError() << "Can't remove last row of File Renamer.\n"; return false; } // Remove widget. Don't delete it since it appears QSignalMapper may still need it. m_rows[id].widget->deleteLater(); m_rows[id].widget = 0; m_rows[id].enableButton = 0; m_rows[id].upButton = 0; m_rows[id].optionsButton = 0; m_rows[id].downButton = 0; int checkboxPosition = 0; // Remove first checkbox. // If not the first row, remove the checkbox before it. if(m_rows[id].position > 0) checkboxPosition = m_rows[id].position - 1; // The checkbox is contained within a layout widget, so the layout // widget is the one the needs to die. delete m_folderSwitches[checkboxPosition]->parent(); m_folderSwitches.erase(&m_folderSwitches[checkboxPosition]); // Go through all the rows and if they have the same category and a // higher categoryNumber, decrement the number. Also update the // position identifier. for(int i = 0; i < m_rows.count(); ++i) { if(i == id) continue; // Don't mess with ourself. if((m_rows[id].category.category == m_rows[i].category.category) && (m_rows[id].category.categoryNumber < m_rows[i].category.categoryNumber)) { --m_rows[i].category.categoryNumber; } // Items are moving up. if(m_rows[id].position < m_rows[i].position) --m_rows[i].position; } // Every row after the one we delete will have a different identifier, since // the identifier is simply its index into m_rows. So we need to re-do the // signal mappings for the affected rows. for(int i = id + 1; i < m_rows.count(); ++i) moveSignalMappings(i, i - 1); m_rows.erase(&m_rows[id]); // Make sure we update the buttons of affected rows. m_rows[idOfPosition(0)].upButton->setEnabled(false); m_rows[idOfPosition(m_rows.count() - 1)].downButton->setEnabled(false); // We can insert another row now, make sure GUI is updated to match. m_ui->m_insertCategory->setEnabled(true); QTimer::singleShot(0, this, SLOT(exampleTextChanged())); return true; } void FileRenamerWidget::addFolderSeparatorCheckbox() { QWidget *temp = new QWidget(m_mainFrame); m_mainFrame->layout()->addWidget(temp); QHBoxLayout *l = new QHBoxLayout(temp); QCheckBox *cb = new QCheckBox(i18n("Insert folder separator"), temp); m_folderSwitches.append(cb); l->addWidget(cb, 0, Qt::AlignCenter); cb->setChecked(false); connect(cb, SIGNAL(toggled(bool)), SLOT(exampleTextChanged())); temp->show(); } void FileRenamerWidget::createTagRows() { KConfigGroup config(KGlobal::config(), "FileRenamer"); QList categoryOrder = config.readEntry("CategoryOrder", QList()); if(categoryOrder.isEmpty()) categoryOrder << Artist << Album << Title << Track; // Setup arrays. m_rows.reserve(categoryOrder.count()); m_folderSwitches.reserve(categoryOrder.count() - 1); mapper = new QSignalMapper(this); mapper->setObjectName( QLatin1String("signal mapper" )); toggleMapper = new QSignalMapper(this); toggleMapper->setObjectName( QLatin1String("toggle mapper" )); upMapper = new QSignalMapper(this); upMapper->setObjectName( QLatin1String("up button mapper" )); downMapper = new QSignalMapper(this); downMapper->setObjectName( QLatin1String("down button mapper" )); connect(mapper, SIGNAL(mapped(int)), SLOT(showCategoryOption(int))); connect(toggleMapper, SIGNAL(mapped(int)), SLOT(slotRemoveRow(int))); connect(upMapper, SIGNAL(mapped(int)), SLOT(moveItemUp(int))); connect(downMapper, SIGNAL(mapped(int)), SLOT(moveItemDown(int))); m_mainFrame = new QFrame(m_ui->m_mainView); m_ui->m_mainView->setWidget(m_mainFrame); m_ui->m_mainView->setWidgetResizable(true); QVBoxLayout *frameLayout = new QVBoxLayout(m_mainFrame); frameLayout->setMargin(10); frameLayout->setSpacing(5); // OK, the deal with the categoryOrder variable is that we need to create // the rows in the order that they were saved in (the order given by categoryOrder). // The signal mappers operate according to the row identifier. To find the position of // a row given the identifier, use m_rows[id].position. To find the id of a given // position, use idOfPosition(position). QList::ConstIterator it = categoryOrder.constBegin(); for(; it != categoryOrder.constEnd(); ++it) { if(*it < StartTag || *it >= NumTypes) { kError() << "Invalid category encountered in file renamer configuration.\n"; continue; } if(m_rows.count() == MAX_CATEGORIES) { kError() << "Maximum number of File Renamer tags reached, bailing.\n"; break; } TagType i = static_cast(*it); addRowCategory(i); // Insert the directory separator checkbox if this isn't the last // item. QList::ConstIterator dup(it); // Check for last item if(++dup != categoryOrder.constEnd()) addFolderSeparatorCheckbox(); } m_rows.first().upButton->setEnabled(false); m_rows.last().downButton->setEnabled(false); // If we have maximum number of categories already, don't let the user // add more. if(m_rows.count() >= MAX_CATEGORIES) m_ui->m_insertCategory->setEnabled(false); } void FileRenamerWidget::exampleTextChanged() { // Just use .mp3 as an example if(m_exampleFromFile && (m_exampleFile.isEmpty() || !FileHandle(m_exampleFile).tag()->isValid())) { m_ui->m_exampleText->setText(i18n("No file selected, or selected file has no tags.")); return; } m_ui->m_exampleText->setText(FileRenamer::fileName(*this) + ".mp3"); } QString FileRenamerWidget::fileCategoryValue(TagType category) const { FileHandle file(m_exampleFile); Tag *tag = file.tag(); switch(category) { case Track: return QString::number(tag->track()); case Year: return QString::number(tag->year()); case Title: return tag->title(); case Artist: return tag->artist(); case Album: return tag->album(); case Genre: return tag->genre(); default: return QString(); } } QString FileRenamerWidget::categoryValue(TagType category) const { if(m_exampleFromFile) return fileCategoryValue(category); const ExampleOptions *example = m_exampleDialog->widget(); switch (category) { case Track: return example->m_exampleTrack->text(); case Year: return example->m_exampleYear->text(); case Title: return example->m_exampleTitle->text(); case Artist: return example->m_exampleArtist->text(); case Album: return example->m_exampleAlbum->text(); case Genre: return example->m_exampleGenre->text(); default: return QString(); } } QList FileRenamerWidget::categoryOrder() const { QList list; // Iterate in GUI row order. for(int i = 0; i < m_rows.count(); ++i) { int rowId = idOfPosition(i); list += m_rows[rowId].category; } return list; } bool FileRenamerWidget::hasFolderSeparator(int index) const { if(index >= m_folderSwitches.count()) return false; return m_folderSwitches[index]->isChecked(); } void FileRenamerWidget::moveItem(int id, MovementDirection direction) { QWidget *l = m_rows[id].widget; int bottom = m_rows.count() - 1; int pos = m_rows[id].position; int newPos = (direction == MoveUp) ? pos - 1 : pos + 1; // Item we're moving can't go further down after this. if((pos == (bottom - 1) && direction == MoveDown) || (pos == bottom && direction == MoveUp)) { int idBottomRow = idOfPosition(bottom); int idAboveBottomRow = idOfPosition(bottom - 1); m_rows[idBottomRow].downButton->setEnabled(true); m_rows[idAboveBottomRow].downButton->setEnabled(false); } // We're moving the top item, do some button switching. if((pos == 0 && direction == MoveDown) || (pos == 1 && direction == MoveUp)) { int idTopItem = idOfPosition(0); int idBelowTopItem = idOfPosition(1); m_rows[idTopItem].upButton->setEnabled(true); m_rows[idBelowTopItem].upButton->setEnabled(false); } // This is the item we're swapping with. int idSwitchWith = idOfPosition(newPos); QWidget *w = m_rows[idSwitchWith].widget; // Update the table of widget rows. std::swap(m_rows[id].position, m_rows[idSwitchWith].position); // Move the item two spaces above/below its previous position. It has to // be 2 spaces because of the checkbox. QBoxLayout *layout = dynamic_cast(m_mainFrame->layout()); if ( !layout ) return; layout->removeWidget(l); layout->insertWidget(2 * newPos, l); // Move the top item two spaces in the opposite direction, for a similar // reason. layout->removeWidget(w); layout->insertWidget(2 * pos, w); layout->invalidate(); QTimer::singleShot(0, this, SLOT(exampleTextChanged())); } int FileRenamerWidget::idOfPosition(int position) const { if(position >= m_rows.count()) { kError() << "Search for position " << position << " out-of-range.\n"; return -1; } for(int i = 0; i < m_rows.count(); ++i) if(m_rows[i].position == position) return i; kError() << "Unable to find identifier for position " << position << endl; return -1; } int FileRenamerWidget::findIdentifier(const CategoryID &category) const { for(int index = 0; index < m_rows.count(); ++index) if(m_rows[index].category == category) return index; kError() << "Unable to find match for category " << TagRenamerOptions::tagTypeText(category.category) << ", number " << category.categoryNumber << endl; return MAX_CATEGORIES; } void FileRenamerWidget::enableAllUpButtons() { for(int i = 0; i < m_rows.count(); ++i) m_rows[i].upButton->setEnabled(true); } void FileRenamerWidget::enableAllDownButtons() { for(int i = 0; i < m_rows.count(); ++i) m_rows[i].downButton->setEnabled(true); } void FileRenamerWidget::showCategoryOption(int id) { TagOptionsDialog *dialog = new TagOptionsDialog(this, m_rows[id].options, m_rows[id].category.categoryNumber); if(dialog->exec() == QDialog::Accepted) { m_rows[id].options = dialog->options(); exampleTextChanged(); } delete dialog; } void FileRenamerWidget::moveItemUp(int id) { moveItem(id, MoveUp); } void FileRenamerWidget::moveItemDown(int id) { moveItem(id, MoveDown); } void FileRenamerWidget::toggleExampleDialog() { m_exampleDialog->setHidden(!m_exampleDialog->isHidden()); } void FileRenamerWidget::insertCategory() { TagType category = static_cast(m_ui->m_category->currentIndex()); if(m_ui->m_category->currentIndex() < 0 || category >= NumTypes) { kError() << "Trying to add unknown category somehow.\n"; return; } // We need to enable the down button of the current bottom row since it // can now move down. int idBottom = idOfPosition(m_rows.count() - 1); m_rows[idBottom].downButton->setEnabled(true); addFolderSeparatorCheckbox(); // Identifier of new row. int id = addRowCategory(category); // Set its down button to be disabled. m_rows[id].downButton->setEnabled(false); m_mainFrame->layout()->invalidate(); m_ui->m_mainView->update(); // Now update according to the code in loadConfig(). m_rows[id].options = TagRenamerOptions(m_rows[id].category); exampleTextChanged(); } void FileRenamerWidget::exampleDialogShown() { m_ui->m_showExample->setText(i18n("Hide Renamer Test Dialog")); } void FileRenamerWidget::exampleDialogHidden() { m_ui->m_showExample->setText(i18n("Show Renamer Test Dialog")); } void FileRenamerWidget::fileSelected(const QString &file) { m_exampleFromFile = true; m_exampleFile = file; exampleTextChanged(); } void FileRenamerWidget::dataSelected() { m_exampleFromFile = false; exampleTextChanged(); } QString FileRenamerWidget::separator() const { return m_ui->m_separator->currentText(); } QString FileRenamerWidget::musicFolder() const { return m_ui->m_musicFolder->url().path(); } void FileRenamerWidget::slotRemoveRow(int id) { // Remove the given identified row. if(!removeRow(id)) kError() << "Unable to remove row " << id << endl; } // // Implementation of FileRenamer // FileRenamer::FileRenamer() { } void FileRenamer::rename(PlaylistItem *item) { PlaylistItemList list; list.append(item); rename(list); } void FileRenamer::rename(const PlaylistItemList &items) { ConfigCategoryReader reader; QStringList errorFiles; QMap map; QMap itemMap; for(PlaylistItemList::ConstIterator it = items.constBegin(); it != items.constEnd(); ++it) { reader.setPlaylistItem(*it); QString oldFile = (*it)->file().absFilePath(); QString extension = (*it)->file().fileInfo().suffix(); QString newFile = fileName(reader) + '.' + extension; if(oldFile != newFile) { map[oldFile] = newFile; itemMap[oldFile] = *it; } } if(itemMap.isEmpty() || ConfirmationDialog(map).exec() != QDialog::Accepted) return; KApplication::setOverrideCursor(Qt::waitCursor); for(QMap::ConstIterator it = map.constBegin(); it != map.constEnd(); ++it) { if(moveFile(it.key(), it.value())) { itemMap[it.key()]->setFile(it.value()); itemMap[it.key()]->refresh(); setFolderIcon(it.value(), itemMap[it.key()]); } else errorFiles << i18n("%1 to %2", it.key(), it.value()); processEvents(); } KApplication::restoreOverrideCursor(); if(!errorFiles.isEmpty()) KMessageBox::errorList(0, i18n("The following rename operations failed:\n"), errorFiles); } bool FileRenamer::moveFile(const QString &src, const QString &dest) { kDebug() << "Moving file " << src << " to " << dest; if(src == dest) return false; // Escape URL. KUrl srcURL = KUrl(src); KUrl dstURL = KUrl(dest); // Clean it. srcURL.cleanPath(); dstURL.cleanPath(); // Make sure it is valid. if(!srcURL.isValid() || !dstURL.isValid()) return false; // Get just the directory. KUrl dir = dstURL; dir.setFileName(QString()); // Create the directory. if(!KStandardDirs::exists(dir.path())) if(!KStandardDirs::makeDir(dir.path())) { kError() << "Unable to create directory " << dir.path() << endl; return false; } // Move the file. KIO::Job *job = KIO::file_move(srcURL, dstURL); return KIO::NetAccess::synchronousRun(job, 0); } void FileRenamer::setFolderIcon(const KUrl &dst, const PlaylistItem *item) { if(item->file().tag()->album().isEmpty() || !item->file().coverInfo()->hasCover()) { return; } KUrl dstURL = dst; dstURL.cleanPath(); // Split path, and go through each path element. If a path element has // the album information, set its folder icon. QStringList elements = dstURL.directory().split('/', QString::SkipEmptyParts); QString path; for(QStringList::ConstIterator it = elements.constBegin(); it != elements.constEnd(); ++it) { path.append('/' + (*it)); kDebug() << "Checking path: " << path; if((*it).contains(item->file().tag()->album() ) && !QFile::exists(path + "/.directory")) { // Seems to be a match, let's set the folder icon for the current // path. First we should write out the file. QPixmap thumb = item->file().coverInfo()->pixmap(CoverInfo::Thumbnail); thumb.save(path + "/.juk-thumbnail.png", "PNG"); KDesktopFile dirFile(path + "/.directory"); KConfigGroup desktopGroup(dirFile.desktopGroup()); if(!desktopGroup.hasKey("Icon")) { desktopGroup.writePathEntry("Icon", QString("%1/.juk-thumbnail.png").arg(path)); dirFile.sync(); } return; } } } /** * Returns iterator pointing to the last item enabled in the given list with * a non-empty value (or is required to be included). */ QList::ConstIterator lastEnabledItem(const QList &list, const CategoryReaderInterface &interface) { QList::ConstIterator it = list.constBegin(); QList::ConstIterator last = list.constEnd(); for(; it != list.constEnd(); ++it) { if(interface.isRequired(*it) || (!interface.isDisabled(*it) && !interface.categoryValue((*it).category).isEmpty())) { last = it; } } return last; } QString FileRenamer::fileName(const CategoryReaderInterface &interface) { const QList categoryOrder = interface.categoryOrder(); const QString separator = interface.separator(); const QString folder = interface.musicFolder(); QList::ConstIterator lastEnabled; int i = 0; QStringList list; QChar dirSeparator (QDir::separator()); // Use lastEnabled to properly handle folder separators. lastEnabled = lastEnabledItem(categoryOrder, interface); bool pastLast = false; // Toggles to true once we've passed lastEnabled. for(QList::ConstIterator it = categoryOrder.constBegin(); it != categoryOrder.constEnd(); ++it, ++i) { if(it == lastEnabled) pastLast = true; if(interface.isDisabled(*it)) continue; QString value = interface.value(*it); // The user can use the folder separator checkbox to add folders, so don't allow // slashes that slip in to accidentally create new folders. Should we filter this // back out when showing it in the GUI? value.replace('/', "%2f"); if(!pastLast && interface.hasFolderSeparator(i)) value.append(dirSeparator); if(interface.isRequired(*it) || !value.isEmpty()) list.append(value); } // Construct a single string representation, handling strings ending in // '/' specially QString result; for(QStringList::ConstIterator it = list.constBegin(); it != list.constEnd(); /* Empty */) { result += *it; ++it; // Manually advance iterator to check for end-of-list. // Add separator unless at a directory boundary if(it != list.constEnd() && !(*it).startsWith(dirSeparator) && // Check beginning of next item. !result.endsWith(dirSeparator)) { result += separator; } } return QString(folder + dirSeparator + result); } #include "filerenamer.moc" // vim: set et sw=4 tw=0 sta: