Commit 0fc1cc57 authored by Scott Wheeler's avatar Scott Wheeler

Add versioning to the cache and clean up the Tag API to be a little more

sane (and to follow the naming conventions from TagLib).

svn path=/trunk/kdemultimedia/juk/; revision=263819
parent 90a67782
......@@ -53,28 +53,24 @@ void Cache::save()
return;
QByteArray data;
QDataStream s(data, IO_WriteOnly);
CacheDataStream s(data, IO_WriteOnly);
for(QDictIterator<Tag>it(*this); it.current(); ++it) {
s << it.current()->absFilePath()
<< *(it.current());
s << it.current()->fileName();
s << *(it.current());
}
f.writeBlock(data);
f.close();
QDataStream fs(&f);
QDir(dirName).rename("cache.new", "cache");
Q_INT32 checksum = qChecksum(data.data(), data.size());
// Store the checksum so that later we can make sure that things are ok.
fs << Q_INT32(m_currentVersion)
<< checksum
<< data;
int checksum = qChecksum(data.data(), data.size());
f.close();
KConfig *config = KGlobal::config();
{
KConfigGroupSaver saver(config, "Cache");
config->writeEntry("Checksum", checksum);
}
config->sync();
QDir(dirName).rename("cache.new", "cache");
}
////////////////////////////////////////////////////////////////////////////////
......@@ -95,39 +91,50 @@ void Cache::load()
if(!f.open(IO_ReadOnly))
return;
QByteArray data = f.readAll();
f.close();
CacheDataStream s(&f);
Q_INT32 version;
s >> version;
QBuffer buffer;
// Compare the checksum of the data to the one stored in the config file to
// make sure that our cache isn't corrupt.
// Do the version specific stuff.
int checksum;
KConfig *config = KGlobal::config();
{
KConfigGroupSaver saver(config, "Cache");
checksum = config->readNumEntry("Checksum", -1);
switch(version) {
case 1: {
s.setCacheVersion(1);
Q_INT32 checksum;
QByteArray data;
s >> checksum
>> data;
buffer.setBuffer(data);
buffer.open(IO_ReadOnly);
s.setDevice(&buffer);
if(checksum != qChecksum(data.data(), data.size())) {
KMessageBox::sorry(0, i18n("The music data cache has been corrupted. JuK "
"needs to rescan it now. This may take some time."));
return;
}
break;
}
default: {
s.device()->reset();
s.setCacheVersion(0);
break;
}
if(checksum >= 0 && checksum != qChecksum(data.data(), data.size())) {
KMessageBox::sorry(0, i18n("The music data cache has been corrupted. JuK "
"needs to rescan it now. This may take some time."));
return;
}
QDataStream s(data, IO_ReadOnly);
// Read the cached tags.
while(!s.atEnd()) {
QString fileName;
s >> fileName;
//fileName.squeeze();
fileName.squeeze();
Tag *t = new Tag(fileName);
s >> *t;
// Just do a dumb read from the cache. Originally cache concistancy was
// checked here, but this means that JuK was having to stat every file
// in the cache while blocking GUI creation. This has since been moved
// to the event loop and is placed in the event loop in the
// CollectionListItem constructor.
}
}
......@@ -20,7 +20,7 @@
#include <qdict.h>
#include "tag.h"
class Tag;
class Cache : public QDict<Tag>
{
......@@ -39,6 +39,27 @@ private:
* size of the dict.
*/
static const int m_cacheSize = 5003;
static const int m_currentVersion = 1;
};
/**
* A simple QDataStream subclass that has an extra field to indicate the cache
* version.
*/
class CacheDataStream : public QDataStream
{
public:
CacheDataStream(QIODevice *d) : QDataStream(d), m_cacheVersion(0) {}
CacheDataStream(QByteArray a, int mode) : QDataStream(a, mode), m_cacheVersion(0) {}
virtual ~CacheDataStream() {}
int cacheVersion() const { return m_cacheVersion; }
void setCacheVersion(int v) { m_cacheVersion = v; }
private:
int m_cacheVersion;
};
#endif
......@@ -43,7 +43,7 @@ void CollectionList::initialize(QWidget *parent, bool restoreOnLoad)
if(restoreOnLoad)
for(QDictIterator<Tag>it(*Cache::instance()); it.current(); ++it)
new CollectionListItem(it.current()->fileInfo(), it.current()->absFilePath());
new CollectionListItem(it.current()->fileInfo(), it.current()->fileName());
}
////////////////////////////////////////////////////////////////////////////////
......
......@@ -198,10 +198,10 @@ QString FileRenamer::rename(const QString &filename, const Tag &tag) const
QString newFilename = m_cfg.filenameScheme();
QMap<QChar, QString> substitutions;
substitutions[ 't' ] = expandToken(Title, tag.track());
substitutions[ 't' ] = expandToken(Title, tag.title());
substitutions[ 'a' ] = expandToken(Artist, tag.artist());
substitutions[ 'A' ] = expandToken(Album, tag.album());
substitutions[ 'T' ] = expandToken(Track, tag.trackNumberString());
substitutions[ 'T' ] = expandToken(Track, QString::number(tag.track()));
substitutions[ 'c' ] = expandToken(Comment, tag.comment());
newFilename = KMacroExpander::expandMacros(newFilename, substitutions);
......
......@@ -1316,7 +1316,7 @@ void Playlist::applyTag(PlaylistItem *item, const QString &text, int column)
switch(column - columnOffset())
{
case PlaylistItem::TrackColumn:
item->tag()->setTrack(text);
item->tag()->setTitle(text);
break;
case PlaylistItem::ArtistColumn:
item->tag()->setArtist(text);
......@@ -1329,7 +1329,7 @@ void Playlist::applyTag(PlaylistItem *item, const QString &text, int column)
bool ok;
int value = text.toInt(&ok);
if(ok)
item->tag()->setTrackNumber(value);
item->tag()->setTrack(value);
break;
}
case PlaylistItem::GenreColumn:
......
......@@ -58,23 +58,23 @@ QString PlaylistItem::text(int column) const
switch(column - offset) {
case TrackColumn:
return m_data->tag()->track();
return m_data->tag()->title();
case ArtistColumn:
return m_data->tag()->artist();
case AlbumColumn:
return m_data->tag()->album();
case TrackNumberColumn:
return m_data->tag()->trackNumberString();
return QString::number(m_data->tag()->track());
case GenreColumn:
return m_data->tag()->genre();
case YearColumn:
return m_data->tag()->yearString();
return QString::number(m_data->tag()->year());
case LengthColumn:
return m_data->tag()->lengthString();
case CommentColumn:
return m_data->tag()->comment();
case FileNameColumn:
return m_data->tag()->absFilePath();
return m_data->tag()->fileName();
default:
return KListViewItem::text(column);
}
......@@ -128,16 +128,16 @@ void PlaylistItem::guessTagInfo(TagGuesser::Type type)
switch(type) {
case TagGuesser::FileName:
{
TagGuesser guesser(tag()->absFilePath());
TagGuesser guesser(tag()->fileName());
if(!guesser.title().isNull())
tag()->setTrack(guesser.title());
tag()->setTitle(guesser.title());
if(!guesser.artist().isNull())
tag()->setArtist(guesser.artist());
if(!guesser.album().isNull())
tag()->setAlbum(guesser.album());
if(!guesser.track().isNull())
tag()->setTrackNumber(guesser.track().toInt());
tag()->setTrack(guesser.track().toInt());
if(!guesser.comment().isNull())
tag()->setComment(guesser.comment());
......@@ -149,7 +149,7 @@ void PlaylistItem::guessTagInfo(TagGuesser::Type type)
{
#if HAVE_MUSICBRAINZ
MusicBrainzQuery *query = new MusicBrainzQuery(MusicBrainzQuery::File,
tag()->absFilePath());
tag()->fileName());
connect(query, SIGNAL(signalDone(const MusicBrainzQuery::TrackList &)),
SLOT(slotTagGuessResults(const MusicBrainzQuery::TrackList &)));
KMainWindow *win = static_cast<KMainWindow *>(kapp->mainWidget());
......@@ -273,9 +273,9 @@ int PlaylistItem::compare(QListViewItem *item, int column, bool ascending) const
int PlaylistItem::compare(const PlaylistItem *firstItem, const PlaylistItem *secondItem, int column, bool) const
{
if(column == TrackNumberColumn) {
if(firstItem->tag()->trackNumber() > secondItem->tag()->trackNumber())
if(firstItem->tag()->track() > secondItem->tag()->track())
return 1;
else if(firstItem->tag()->trackNumber() < secondItem->tag()->trackNumber())
else if(firstItem->tag()->track() < secondItem->tag()->track())
return -1;
else
return 0;
......@@ -348,13 +348,13 @@ void PlaylistItem::slotTagGuessResults(const MusicBrainzQuery::TrackList &res)
MusicBrainzQuery::Track track = trackPicker->selectedTrack();
if(!track.name.isEmpty())
tag()->setTrack(track.name);
tag()->setTitle(track.name);
if(!track.artist.isEmpty())
tag()->setArtist(track.artist);
if(!track.album.isEmpty())
tag()->setAlbum(track.album);
if(track.number)
tag()->setTrackNumber(track.number);
tag()->setTrack(track.number);
tag()->save();
slotRefresh();
......
......@@ -150,7 +150,7 @@ void PlaylistSplitter::populatePlayHistoryMenu(QPopupMenu* menu, bool random)
menu->clear();
int i = 0;
for(PlaylistItemList::Iterator it = list.begin(); it != list.end(); ++it)
menu->insertItem((*it)->tag()->track(), ++i);
menu->insertItem((*it)->tag()->title(), ++i);
}
QString PlaylistSplitter::playSelectedFile()
......
......@@ -24,8 +24,8 @@
#include <taglib/vorbisfile.h>
#include <taglib/xiphcomment.h>
#include "tag.h"
#include "cache.h"
#include "tag.h"
#include "mediafiles.h"
#include "stringshare.h"
......@@ -65,7 +65,7 @@ Tag *Tag::createTag(const QString &fileName, bool ignoreCache)
Tag::~Tag()
{
Cache::instance()->remove(absFilePath());
Cache::instance()->remove(m_fileName);
}
void Tag::save()
......@@ -80,38 +80,81 @@ bool Tag::current() const
m_modificationTime >= Tag::lastModified());
}
QDataStream &Tag::read(QDataStream &s)
QDateTime Tag::lastModified() const
{
static QString dummyString;
static int dummyInt;
// TODO: Use Q_UINT32 in place of all integers.
s >> dummyInt // TODO: remove
>> m_title
>> m_artist
>> m_album
>> m_genre
>> dummyInt // TODO: remove
>> m_track
>> dummyString // TODO: remove
>> m_year
>> dummyString // TODO: remove
>> m_comment
>> m_bitrateString // TODO: remove and replace with int
>> m_lengthString
>> m_seconds
>> dummyString // TODO: remove
>> m_modificationTime;
if(m_lastModified.isNull())
m_lastModified = m_info.lastModified();
return m_lastModified;
}
CacheDataStream &Tag::read(CacheDataStream &s)
{
switch(s.cacheVersion()) {
case 1: {
Q_INT32 track;
Q_INT32 year;
Q_INT32 bitrate;
Q_INT32 seconds;
s >> m_title
>> m_artist
>> m_album
>> m_genre
>> track
>> year
>> m_comment
>> bitrate
>> m_lengthString
>> seconds
>> m_modificationTime;
m_track = track;
m_year = year;
m_bitrate = bitrate;
m_seconds = seconds;
break;
}
default: {
static QString dummyString;
static int dummyInt;
QString bitrateString;
s >> dummyInt
>> m_title
>> m_artist
>> m_album
>> m_genre
>> dummyInt
>> m_track
>> dummyString
>> m_year
>> dummyString
>> m_comment
>> bitrateString
>> m_lengthString
>> m_seconds
>> dummyString
>> m_modificationTime;
bool ok;
m_bitrate = bitrateString.toInt(&ok);
if(!ok)
m_bitrate = 0;
break;
}
}
// Try to reduce memory usage: share tags that frequently repeat, squeeze others
m_title.squeeze();
m_lengthString.squeeze();
m_comment = StringShare::tryShare(m_comment);
m_artist = StringShare::tryShare(m_artist);
m_album = StringShare::tryShare(m_album);
m_genre = StringShare::tryShare(m_genre);
m_bitrateString = StringShare::tryShare(m_bitrateString);
m_lengthString.squeeze();
return s;
}
......@@ -145,7 +188,6 @@ Tag::Tag(const QString &fileName, TagLib::File *file) :
m_seconds = file->audioProperties()->length();
m_bitrate = file->audioProperties()->length();
m_bitrateString = QString::number(m_bitrate);
const int seconds = m_seconds % 60;
const int minutes = (m_seconds - seconds) / 60;
......@@ -159,31 +201,24 @@ Tag::Tag(const QString &fileName, TagLib::File *file) :
// related functions
////////////////////////////////////////////////////////////////////////////////
QDataStream &operator<<(QDataStream &s, const Tag &t)
CacheDataStream &operator<<(CacheDataStream &s, const Tag &t)
{
// TODO: Use Q_UINT32 in place of all integers.
s << 0 // TODO: remove
<< t.track()
s << t.title()
<< t.artist()
<< t.album()
<< t.genre()
<< 0 // TODO: remove
<< t.trackNumber()
<< QString::null // TODO: remove
<< t.year()
<< QString::null // TODO: remove
<< Q_INT32(t.track())
<< Q_INT32(t.year())
<< t.comment()
<< t.bitrateString() // TODO: remove and replace with int
<< Q_INT32(t.bitrate())
<< t.lengthString()
<< t.seconds()
<< QString::null // TODO: remove
<< Q_INT32(t.seconds())
<< t.lastModified();
return s;
}
QDataStream &operator>>(QDataStream &s, Tag &t)
CacheDataStream &operator>>(CacheDataStream &s, Tag &t)
{
return t.read(s);
}
......@@ -26,6 +26,8 @@
namespace TagLib { class File; }
class CacheDataStream;
/*!
* This should really be called "metadata" and may at some point be titled as
* such. Right now it's mostly a Qt wrapper around TagLib.
......@@ -46,47 +48,43 @@ public:
void save();
QString track() const { return m_title; }
QString title() const { return m_title; }
QString artist() const { return m_artist; }
QString album() const { return m_album; }
QString genre() const { return m_genre; }
int trackNumber() const { return m_track; }
QString trackNumberString() const { return m_track > 0 ? QString::number(m_track) : QString::null; }
int track() const { return m_track; }
int year() const { return m_year; }
QString yearString() const { return m_year > 0 ? QString::number(m_year) : QString::null; }
QString comment() const { return m_comment; }
void setTrack(const QString &value) { m_title = value; }
void setTitle(const QString &value) { m_title = value; }
void setArtist(const QString &value) { m_artist = value; }
void setAlbum(const QString &value) { m_album = value; }
void setGenre(const QString &value) { m_genre = value; }
void setTrackNumber(int value) { m_track = value; }
void setTrack(int value) { m_track = value; }
void setYear(int value) { m_year = value; }
void setComment(const QString &value) { m_comment = value; }
QString bitrateString() const { return m_bitrateString; }
QString lengthString() const { return m_lengthString; }
int seconds() const { return m_seconds; }
int bitrate() const { return m_bitrate; }
QString fileName() const { return m_fileName; }
QDateTime lastModified() const;
/**
* As a convenience, since producing a length string from a number of second
* isn't a one liner, provide the lenght in string form.
*/
QString lengthString() const { return m_lengthString; }
/**
* Check to see if the item is up to date.
*/
bool current() const;
// These functions are inlined because they are used on startup -- the most
// performance critical section of JuK.
inline QString absFilePath() const { return m_fileName; }
inline QDateTime lastModified() const
{
if(m_lastModified.isNull())
m_lastModified = m_info.lastModified();
return m_lastModified;
}
inline bool fileExists() const { return m_info.exists() && m_info.isFile(); }
inline QFileInfo fileInfo() const { return m_info; }
bool fileExists() const { return m_info.exists() && m_info.isFile(); }
QFileInfo fileInfo() const { return m_info; }
QDataStream &read(QDataStream &s);
CacheDataStream &read(CacheDataStream &s);
private:
/*!
......@@ -108,12 +106,12 @@ private:
int m_year;
int m_seconds;
int m_bitrate;
QString m_lengthString;
QString m_bitrateString;
QDateTime m_modificationTime;
QString m_lengthString;
};
QDataStream &operator<<(QDataStream &s, const Tag &t);
QDataStream &operator>>(QDataStream &s, Tag &t);
CacheDataStream &operator<<(CacheDataStream &s, const Tag &t);
CacheDataStream &operator>>(CacheDataStream &s, Tag &t);
#endif
......@@ -83,11 +83,11 @@ void TagEditor::slotRefresh()
Tag *tag = item->tag();
m_artistNameBox->setEditText(tag->artist());
m_trackNameBox->setText(tag->track());
m_trackNameBox->setText(tag->title());
m_albumNameBox->setEditText(tag->album());
m_fileNameBox->setText(item->fileName());
m_bitrateBox->setText(tag->bitrateString());
m_bitrateBox->setText(QString::number(tag->bitrate()));
m_lengthBox->setText(tag->lengthString());
if(m_genreList.findIndex(tag->genre()) >= 0)
......@@ -97,7 +97,7 @@ void TagEditor::slotRefresh()
m_genreBox->setEditText(tag->genre());
}
m_trackSpin->setValue(tag->trackNumber());
m_trackSpin->setValue(tag->track());
m_yearSpin->setValue(tag->year());
m_commentBox->setText(tag->comment());
......@@ -145,7 +145,7 @@ void TagEditor::slotRefresh()
m_artistNameBox->lineEdit()->clear();
m_enableBoxes[m_artistNameBox]->setChecked(false);
}
if(m_trackNameBox->text() != tag->track() &&
if(m_trackNameBox->text() != tag->title() &&
m_enableBoxes.contains(m_trackNameBox))
{
m_trackNameBox->clear();
......@@ -163,7 +163,7 @@ void TagEditor::slotRefresh()
m_genreBox->lineEdit()->clear();
m_enableBoxes[m_genreBox]->setChecked(false);
}
if(m_trackSpin->value() != tag->trackNumber() &&
if(m_trackSpin->value() != tag->track() &&
m_enableBoxes.contains(m_trackSpin))
{
m_trackSpin->setValue(0);
......@@ -460,11 +460,11 @@ void TagEditor::save(const PlaylistItemList &list)
if(m_enableBoxes[m_artistNameBox]->isOn())
item->tag()->setArtist(m_artistNameBox->currentText());
if(m_enableBoxes[m_trackNameBox]->isOn())
item->tag()->setTrack(m_trackNameBox->text());
item->tag()->setTitle(m_trackNameBox->text());
if(m_enableBoxes[m_albumNameBox]->isOn())
item->tag()->setAlbum(m_albumNameBox->currentText());
if(m_enableBoxes[m_trackSpin]->isOn())
item->tag()->setTrackNumber(m_trackSpin->value());
item->tag()->setTrack(m_trackSpin->value());
if(m_enableBoxes[m_yearSpin]->isOn())
item->tag()->setYear(m_yearSpin->value());
if(m_enableBoxes[m_commentBox]->isOn())
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment