/*************************************************************************** * Copyright (C) 2009-2021 by Ilya Kotov * * forkotov02@ya.ru * * * * 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, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fileops.h" #define COPY_BLOCK_SIZE 102400 FileOps::FileOps(QObject *parent) : QObject(parent) { //separators QAction *separator1 = new QAction(this); separator1->setSeparator(true); QAction *separator2 = new QAction(this); separator2->setSeparator(true); //load settings QSettings settings(Qmmp::configFile(), QSettings::IniFormat); settings.beginGroup("FileOps"); if(!settings.value("name_0").isNull()) UiHelper::instance()->addAction(separator1, UiHelper::PLAYLIST_MENU); else return; int i = 0; while(!settings.value(QString("name_%1").arg(i)).isNull()) { QString name = settings.value(QString("name_%1").arg(i)).toString(); QVariantMap data = { { "action", settings.value(QString("action_%1").arg(i), FileOps::COPY).toInt() }, { "pattern", settings.value(QString("pattern_%1").arg(i)).toString() }, { "destination", settings.value(QString("destination_%1").arg(i)).toString() }, { "command", settings.value(QString("command_%1").arg(i)).toString() }, }; if(settings.value(QString("enabled_%1").arg(i), true).toBool()) { QAction *action = new QAction(name, this); action->setData(data); action->setShortcut(settings.value(QString("hotkey_%1").arg(i)).toString()); connect(action, &QAction::triggered, this, &FileOps::execAction); UiHelper::instance()->addAction(action, UiHelper::PLAYLIST_MENU); } ++i; } settings.endGroup(); UiHelper::instance()->addAction(separator2, UiHelper::PLAYLIST_MENU); } FileOps::~FileOps() {} void FileOps::execAction() { QAction *action = qobject_cast(sender()); QVariantMap data = action->data().toMap(); int type = data["action"].toInt(); QString pattern = data["pattern"].toString(); QString destination = data["destination"].toString(); QString command = data["command"].toString(); MetaDataFormatter formatter(type == EXECUTE ? command : pattern); PlayListModel *model = MediaPlayer::instance()->playListManager()->selectedPlayList(); const QList tracks = model->selectedTracks(); switch (type) { case COPY: { qDebug("FileOps: copy"); if(!QDir(destination).exists ()) { QMessageBox::critical (qApp->activeWindow (), tr("Error"), tr("Destination directory doesn't exist")); break; } copy(tracks, destination, &formatter); break; } case RENAME: { qDebug("FileOps: rename"); rename(tracks, &formatter, model); break; } case REMOVE: { qDebug("FileOps: remove"); if(QMessageBox::question (qApp->activeWindow (), tr("Remove Files"), tr("Are you sure you want to remove %n file(s) from disk?", "",tracks.size()), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) break; if(PlayListManager::instance()->selectedPlayList() != model) break; for(PlayListTrack *track : qAsConst(tracks)) { if(PlayListManager::instance()->selectedPlayList() != model) break; if(isValid(track) && QFile::exists(track->path()) && QFile::remove(track->path())) model->removeTrack(track); } break; } case MOVE: { qDebug("FileOps: move"); if(!QDir(destination).exists ()) { QMessageBox::critical (qApp->activeWindow (), tr("Error"), tr("Destination directory doesn't exist")); break; } if(QMessageBox::question (qApp->activeWindow (), tr("Move Files"), tr("Are you sure you want to move %n file(s)?", "",tracks.size()), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { break; } move(tracks, destination, &formatter, model); break; } case EXECUTE: { qDebug("FileOps: execute"); execute(tracks, &formatter, model); break; } } } void FileOps::copy(const QList &tracks, const QString &dest, const MetaDataFormatter *formatter) { QProgressDialog progress(qApp->activeWindow ()); progress.setWindowModality(Qt::WindowModal); progress.setWindowTitle(tr("Copying")); progress.setCancelButtonText(tr("Stop")); progress.show(); progress.setAutoClose(false); int i = 0; for(PlayListTrack *track : qAsConst(tracks)) { if(!isValid(track) || !QFile::exists(track->path())) continue; QString fileName = formatter->format(track); //generate file name QString ext = QString(".") + track->path().section(".", -1).toLower(); if(!ext.isEmpty() && !fileName.endsWith(ext, Qt::CaseInsensitive)) fileName += ext; //append extension //create destination path QString path = dest + "/" + fileName; QDir dir = QFileInfo(path).dir(); if(!dir.exists()) { if(!dir.mkpath(dir.absolutePath())) { qWarning("FileOps: unable to create directory"); continue; } } if(track->path() == path) continue; //copy file QFile in(track->path()); QFile out(path); if(!in.open(QIODevice::ReadOnly)) { qWarning("FileOps: %s", qPrintable(in.errorString ())); continue; } if(!out.open(QIODevice::WriteOnly)) { qWarning("FileOps: %s", qPrintable(out.errorString ())); continue; } progress.setMaximum(int(in.size() / COPY_BLOCK_SIZE)); progress.setValue(0); progress.setLabelText (QString(tr("Copying file %1/%2")).arg(++i).arg(tracks.size())); progress.update(); while (!in.atEnd ()) { out.write(in.read(COPY_BLOCK_SIZE)); progress.setValue(int(out.size() / COPY_BLOCK_SIZE)); qApp->processEvents(); } if(progress.wasCanceled ()) break; } progress.close(); } void FileOps::rename(const QList &tracks, const MetaDataFormatter *formatter, PlayListModel *model) { for(PlayListTrack *track : qAsConst(tracks)) { if(!isValid(track) || !QFile::exists(track->path())) //is it file? continue; if(PlayListManager::instance()->selectedPlayList() != model) break; QString fileName = formatter->format(track); //generate file name QString ext = QString(".") + track->path().section(".", -1).toLower(); if(!ext.isEmpty() && !fileName.endsWith(ext, Qt::CaseInsensitive)) fileName += ext; //append extension //rename file QFile file(track->path()); QString dest = QFileInfo(track->path()).absolutePath (); if(isValid(track) && file.rename(dest + "/" + fileName) && isValid(track)) { track->setPath(dest + "/" + fileName); track->updateMetaData(); model->doCurrentVisibleRequest(); } else continue; } } void FileOps::move(const QList &tracks, const QString &dest, const MetaDataFormatter *formatter, PlayListModel *model) { QProgressDialog progress(qApp->activeWindow ()); progress.setWindowModality(Qt::WindowModal); progress.setWindowTitle(tr("Moving")); progress.setCancelButtonText(tr("Stop")); progress.show(); progress.setAutoClose (false); int i = 0; for(PlayListTrack *track : qAsConst(tracks)) { if(!isValid(track) || !QFile::exists(track->path())) continue; if(PlayListManager::instance()->selectedPlayList() != model) break; QString fileName = formatter->format(track); //generate file name QString ext = QString(".") + track->path().section(".", -1).toLower(); if(!ext.isEmpty() && !fileName.endsWith(ext, Qt::CaseInsensitive)) fileName += ext; //append extension //create destination path QString path = dest + "/" + fileName; //skip moved files if(path == track->path()) continue; QDir dir = QFileInfo(path).dir(); if(!dir.exists()) { if(!dir.mkpath(dir.absolutePath())) { qWarning("FileOps: unable to create directory"); continue; } } progress.setRange(0, 100); progress.setValue(0); progress.setLabelText (QString(tr("Moving file %1/%2")).arg(++i).arg(tracks.size())); progress.update(); if(!isValid(track) || PlayListManager::instance()->selectedPlayList() != model) { progress.setValue(100); continue; } //try to rename file first if(QFile::rename(track->path(), path)) { progress.setValue(100); track->setPath(path); model->doCurrentVisibleRequest(); continue; } //copy file QFile in(track->path()); QFile out(path); if(!in.open(QIODevice::ReadOnly)) { qWarning("FileOps: %s", qPrintable(in.errorString ())); continue; } if(!out.open(QIODevice::WriteOnly)) { qWarning("FileOps: %s", qPrintable(out.errorString ())); continue; } progress.setMaximum(int(in.size() / COPY_BLOCK_SIZE)); progress.setValue(0); progress.update(); while (!in.atEnd ()) { progress.wasCanceled (); out.write(in.read(COPY_BLOCK_SIZE)); progress.setValue(int(out.size() / COPY_BLOCK_SIZE)); qApp->processEvents(); } in.close(); if(!isValid(track) || PlayListManager::instance()->selectedPlayList() != model) continue; if(!QFile::remove(track->path())) qWarning("FileOps: unable to remove file '%s'", qPrintable(track->path())); track->setPath(path); model->doCurrentVisibleRequest(); if(progress.wasCanceled()) break; } progress.close(); } void FileOps::execute(const QList &tracks, const MetaDataFormatter *formatter, PlayListModel *model) { for(PlayListTrack *track : qAsConst(tracks)) { if(!isValid(track) || !QFile::exists(track->path())) //is it file? continue; if(PlayListManager::instance()->selectedPlayList() != model) break; QString command = formatter->format(track); //generate file name qDebug("FileOps: exec command: %s", qPrintable(command)); #ifdef Q_OS_WIN QProcess::startDetached(QString("cmd.exe /C %1").arg(command)); #else QStringList args = { "-c", command }; QProcess::startDetached("sh", args); #endif } } bool FileOps::isValid(PlayListTrack *track) const { const QList tracks = PlayListManager::instance()->selectedPlayList()->selectedTracks(); return tracks.contains(track); }