filerenamer.cpp 8.49 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
/*
 * filerenamer.cpp - (c) 2003 Frerich Raabe <raabe@kde.org>
 *
 * 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.
 */
#include "filerenamer.h"
#include "playlistitem.h"
11
#include "tag.h"
12 13 14

#include <kapplication.h>
#include <kconfig.h>
15
#include <kdialogbase.h>
Scott Wheeler's avatar
build  
Scott Wheeler committed
16
#include <kdebug.h>
17
#include <kiconloader.h>
18
#include <kio/netaccess.h>
19
#include <klocale.h>
20
#include <kmacroexpander.h>
21
#include <kmessagebox.h>
22
#include <kstandarddirs.h>
23 24

#include <qdir.h>
25 26 27 28
#include <qhbox.h>
#include <qheader.h>
#include <qlabel.h>
#include <qvbox.h>
29 30
#include <qpainter.h>
#include <qsimplerichtext.h>
31
#include <qstylesheet.h>
32 33 34 35 36 37 38 39 40 41 42 43 44

class FileRenamer::ConfirmationDialog : public KDialogBase
{
public:
    ConfirmationDialog(const QMap<QString, QString> &files,
                       QWidget *parent = 0, const char *name = 0)
        : KDialogBase(parent, name, true, i18n("Warning"), Ok | Cancel)
    {
        QVBox *vbox = makeVBoxMainWidget();
        QHBox *hbox = new QHBox(vbox);

        QLabel *l = new QLabel(hbox);
        l->setPixmap(SmallIcon("messagebox_warning", 32));
45

Jonathan Riddell's avatar
Jonathan Riddell committed
46
        l = new QLabel(i18n("You are about to rename the following files. "
47 48 49 50 51 52 53 54 55
                            "Are you sure you want to continue?"), hbox);
        hbox->setStretchFactor(l, 1);

        KListView *lv = new KListView(vbox);

        lv->addColumn(i18n("Original Name"));
        lv->addColumn(i18n("New Name"));

        int lvHeight = 0;
56

57 58
        QMap<QString, QString>::ConstIterator it = files.begin();
        for(; it != files.end(); ++it) {
59
            KListViewItem *i = it.key() != it.data()
60 61
                ? new KListViewItem(lv, it.key(), it.data())
                : new KListViewItem(lv, it.key(), i18n("No Change"));
62 63 64 65
            lvHeight += i->height();
        }

        lvHeight += lv->horizontalScrollBar()->height() + lv->header()->height();
66
        lv->setMinimumHeight(QMIN(lvHeight, 400));
67 68 69
        resize(QMIN(width(), 500), QMIN(minimumHeight(), 400));
    }
};
70

71 72
FileRenamer::Config::Config(KConfigBase *cfg)
    : m_grp(cfg, "FileRenamer")
73 74 75
{
}

76
QString FileRenamer::Config::filenameScheme() const
77
{
78
    return m_grp.readEntry("FilenameScheme", QDir::homeDirPath() + "/Music/%a%A%T%t%c");
79 80
}

81
void FileRenamer::Config::setFilenameScheme(const QString &scheme)
82
{
83
    m_grp.writeEntry("FilenameScheme", scheme);
84 85
}

86
QString FileRenamer::Config::getToken(TokenType type) const
87
{
88 89 90 91 92 93 94 95 96
    QString fallback;
    switch(type) {
        case Title: fallback = "%s"; break;
        case Artist: fallback = "%s/"; break;
        case Album: fallback = "%s/"; break;
        case Track: fallback = "[%s] "; break;
        case Comment: fallback = " (%s)"; break;
    }
    return m_grp.readEntry(tokenToString(type) + "Token", fallback);
97
}
98

99 100 101 102
void FileRenamer::Config::setToken(TokenType type, const QString &value)
{
    m_grp.writeEntry(tokenToString(type) + "Token", value);
}
103

104 105
bool FileRenamer::Config::tokenNeedsValue(TokenType type) const
{
106 107
    bool fallback = type != Title ? true : false;
    return m_grp.readBoolEntry("Need" + tokenToString(type) + "Value", fallback);
108 109
}

110
void FileRenamer::Config::setTokenNeedsValue(TokenType type, bool needsValue)
111
{
112
    m_grp.writeEntry("Need" + tokenToString(type) + "Value", needsValue);
113 114
}

115
QString FileRenamer::tokenToString(TokenType type)
116
{
117 118 119 120 121 122 123 124
    switch(type) {
        case Title: return "Title";
        case Artist: return "Artist";
        case Album: return "Album";
        case Track: return "Track";
        case Comment: return "Comment";
    }
    return QString::null;
125 126
}

127 128
FileRenamer::FileRenamer()
    : m_cfg(kapp->config())
129 130 131
{
}

132
FileRenamer::FileRenamer(PlaylistItem *item)
133
    : m_cfg(kapp->config())
134
{
135
    rename(item);
136 137
}

138
QString FileRenamer::expandToken(TokenType type, const QString &value_) const
139
{
140 141
    const bool needValue = m_cfg.tokenNeedsValue(type);

142
    QString value = value_;
143
    QString token = m_cfg.getToken(type);
144
    if(value.find(QDir::separator()) > -1) {
145
        kdWarning() << "Found token value with dir separators!" << endl;
146
        value.replace(QDir::separator(), "");
147
    }
148

149
    if((type == Track) && needValue && (value.toUInt() == 0))
Scott Wheeler's avatar
Scott Wheeler committed
150
        return QString::null;
151

152
    if((needValue) && value.isEmpty())
Scott Wheeler's avatar
Scott Wheeler committed
153
        return QString::null;
154 155

    token.replace("%s", value);
156
    return token;
157 158
}

159
void FileRenamer::rename(PlaylistItem *item)
160
{
161
    if(item == 0 || item->file().tag() == 0)
162 163
        return;

164
    QString newFilename = rename(item->file().absFilePath(), *item->file().tag());
165 166 167 168 169

    // Don't try to rename the file if the name didn't change.
    if(QFileInfo(newFilename).absFilePath() == item->file().absFilePath())
        return;

170 171 172 173 174 175 176 177
    QString message = i18n("<qt>You are about to rename the file<br/><br/>'%1'<br/><br/> to <br/>"
                           "<br/>'%2'<br/><br/>Are you sure you want to continue?</qt>");
    message = message.arg(QStyleSheet::escape(item->file().absFilePath()));
    message = message.arg(QStyleSheet::escape(newFilename));

    if(KMessageBox::warningContinueCancel(0, message, i18n("Warning"), KStdGuiItem::cont(),
                                          "ShowFileRenamerWarning") == KMessageBox::Continue)
    {
178 179
        if(moveFile(item->file().absFilePath(), newFilename))
            item->setFile(FileHandle(newFilename));
180
        else
181 182
            KMessageBox::error(0, i18n("<qt>Failed to rename the file<br/><br/>'%1'<br/><br/>to<br/><br/>'%2'</qt>")
                               .arg(item->file().absFilePath()).arg(newFilename));
183 184 185 186 187 188
    }
}

void FileRenamer::rename(const PlaylistItemList &items)
{
    QMap<QString, QString> map;
189
    QMap<QString, PlaylistItem *> itemMap;
190
    QStringList errorFiles;
191 192 193

    PlaylistItemList::ConstIterator it = items.begin();
    for(; it != items.end(); ++it) {
194
        if(!*it || !(*it)->file().tag())
195 196
            continue;

197
        const QString oldName = (*it)->file().absFilePath();
198
        map[oldName] = rename(oldName, *(*it)->file().tag());
199
        itemMap[oldName] = *it;
200 201
    }

202 203
    if(ConfirmationDialog(map).exec() == QDialog::Accepted) {

204 205 206 207
        KApplication::setOverrideCursor(Qt::waitCursor);
        int j = 1;
        QMap<QString, QString>::ConstIterator it = map.begin();
        for(; it != map.end(); ++it, ++j) {
208 209 210 211
            // Don't rename a file if the name didn't change.
            if(QFileInfo(it.key()).absFilePath() == QFileInfo(it.data()).absFilePath())
                continue;

212
            if(moveFile(it.key(), it.data()))
213
                itemMap[it.key()]->setFile(FileHandle(it.data()));
214
            else
215
                errorFiles << QString("%1 to %2").arg(it.key()).arg(it.data());
216

217 218 219 220 221
            if(j % 5 == 0)
                kapp->processEvents();
        }
        KApplication::restoreOverrideCursor();
    }
222 223 224

    if(!errorFiles.isEmpty())
        KMessageBox::error(0, i18n("The following rename operations failed:\n") + errorFiles.join("\n"));
225 226 227 228
}

QString FileRenamer::rename(const QString &filename, const Tag &tag) const
{
229
    QString newFilename = m_cfg.filenameScheme();
230 231

    QMap<QChar, QString> substitutions;
232
    substitutions[ 't' ] = expandToken(Title, tag.title());
233 234
    substitutions[ 'a' ] = expandToken(Artist, tag.artist());
    substitutions[ 'A' ] = expandToken(Album, tag.album());
235
    substitutions[ 'T' ] = expandToken(Track, QString::number(tag.track()));
236
    substitutions[ 'c' ] = expandToken(Comment, tag.comment());
237 238 239 240 241

    newFilename = KMacroExpander::expandMacros(newFilename, substitutions);
    newFilename = newFilename.stripWhiteSpace();

    if(QFileInfo(newFilename).isRelative())
242
        newFilename = filename.left( filename.findRev( "/" ) )
243
            + "/" + newFilename;
244
    newFilename += "." + QFileInfo(filename).extension(false);
245

246
    return newFilename;
247 248
}

249
bool FileRenamer::moveFile(const QString &src, const QString &dest)
250
{
Zack Rusin's avatar
Zack Rusin committed
251
    kdDebug(65432) << "Moving file " << src << " to " << dest << endl;
252 253

    if(src == dest)
254
        return false;
255

256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
    // Escape URL.
    KURL srcURL = KURL::fromPathOrURL(src);
    KURL dstURL = KURL::fromPathOrURL(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::null);

    // Create the directory.
    if(!KStandardDirs::exists(dir.path()))
        if(!KStandardDirs::makeDir(dir.path()))
            return false;
276

277 278
    // Move the file.
    return KIO::NetAccess::file_move(srcURL, dstURL);
279
}
280 281

// vim:ts=4:sw=4:et