CUE songs are now properly updated in library - you can delete a CUE sheet, add it, you can change section markers in it etc. and everything should work as expected
Song now knows it's cue path (if any)
This commit is contained in:
parent
5c29a62b19
commit
ddd3f119d3
@ -294,6 +294,7 @@
|
|||||||
<file>schema/jamendo.sql</file>
|
<file>schema/jamendo.sql</file>
|
||||||
<file>schema/schema-23.sql</file>
|
<file>schema/schema-23.sql</file>
|
||||||
<file>schema/schema-24.sql</file>
|
<file>schema/schema-24.sql</file>
|
||||||
|
<file>schema/schema-25.sql</file>
|
||||||
<file>pythonstartup.py</file>
|
<file>pythonstartup.py</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
@ -49,7 +49,9 @@ CREATE TABLE device_%deviceid_songs (
|
|||||||
|
|
||||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||||
score INTEGER NOT NULL DEFAULT 0,
|
score INTEGER NOT NULL DEFAULT 0,
|
||||||
beginning NOT NULL DEFAULT 0
|
beginning NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
cue_path TEXT
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX idx_device_%deviceid_songs_album ON device_%deviceid_songs (album);
|
CREATE INDEX idx_device_%deviceid_songs_album ON device_%deviceid_songs (album);
|
||||||
|
@ -36,7 +36,9 @@ CREATE TABLE jamendo.songs (
|
|||||||
effective_compilation NOT NULL DEFAULT 0,
|
effective_compilation NOT NULL DEFAULT 0,
|
||||||
skipcount NOT NULL DEFAULT 0,
|
skipcount NOT NULL DEFAULT 0,
|
||||||
score NOT NULL DEFAULT 0,
|
score NOT NULL DEFAULT 0,
|
||||||
beginning NOT NULL DEFAULT 0
|
beginning NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
cue_path TEXT
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE VIRTUAL TABLE jamendo.songs_fts USING fts3(
|
CREATE VIRTUAL TABLE jamendo.songs_fts USING fts3(
|
||||||
|
3
data/schema/schema-25.sql
Normal file
3
data/schema/schema-25.sql
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
ALTER TABLE %allsongstables ADD COLUMN cue_path TEXT;
|
||||||
|
|
||||||
|
UPDATE schema_version SET version=25;
|
@ -31,7 +31,7 @@
|
|||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
|
|
||||||
const char* Database::kDatabaseFilename = "clementine.db";
|
const char* Database::kDatabaseFilename = "clementine.db";
|
||||||
const int Database::kSchemaVersion = 24;
|
const int Database::kSchemaVersion = 25;
|
||||||
const char* Database::kMagicAllSongsTables = "%allsongstables";
|
const char* Database::kMagicAllSongsTables = "%allsongstables";
|
||||||
|
|
||||||
int Database::sNextConnectionId = 1;
|
int Database::sNextConnectionId = 1;
|
||||||
|
@ -93,7 +93,8 @@ const QStringList Song::kColumns = QStringList()
|
|||||||
<< "mtime" << "ctime" << "filesize" << "sampler" << "art_automatic"
|
<< "mtime" << "ctime" << "filesize" << "sampler" << "art_automatic"
|
||||||
<< "art_manual" << "filetype" << "playcount" << "lastplayed" << "rating"
|
<< "art_manual" << "filetype" << "playcount" << "lastplayed" << "rating"
|
||||||
<< "forced_compilation_on" << "forced_compilation_off"
|
<< "forced_compilation_on" << "forced_compilation_off"
|
||||||
<< "effective_compilation" << "skipcount" << "score" << "beginning" << "length";
|
<< "effective_compilation" << "skipcount" << "score" << "beginning" << "length"
|
||||||
|
<< "cue_path";
|
||||||
|
|
||||||
const QString Song::kColumnSpec = Song::kColumns.join(", ");
|
const QString Song::kColumnSpec = Song::kColumns.join(", ");
|
||||||
const QString Song::kBindSpec = Prepend(":", Song::kColumns).join(", ");
|
const QString Song::kBindSpec = Prepend(":", Song::kColumns).join(", ");
|
||||||
@ -506,6 +507,8 @@ void Song::InitFromQuery(const SqlRow& q, int col) {
|
|||||||
d->beginning_ = q.value(col + 32).isNull() ? 0 : q.value(col + 32).toInt();
|
d->beginning_ = q.value(col + 32).isNull() ? 0 : q.value(col + 32).toInt();
|
||||||
set_length(toint(col + 33));
|
set_length(toint(col + 33));
|
||||||
|
|
||||||
|
d->cue_path_ = tostr(col + 34);
|
||||||
|
|
||||||
#undef tostr
|
#undef tostr
|
||||||
#undef toint
|
#undef toint
|
||||||
#undef tofloat
|
#undef tofloat
|
||||||
@ -964,6 +967,8 @@ void Song::BindToQuery(QSqlQuery *query) const {
|
|||||||
query->bindValue(":beginning", d->beginning_);
|
query->bindValue(":beginning", d->beginning_);
|
||||||
query->bindValue(":length", intval(length()));
|
query->bindValue(":length", intval(length()));
|
||||||
|
|
||||||
|
query->bindValue(":cue_path", d->cue_path_);
|
||||||
|
|
||||||
#undef intval
|
#undef intval
|
||||||
#undef notnullintval
|
#undef notnullintval
|
||||||
}
|
}
|
||||||
@ -1057,7 +1062,8 @@ bool Song::IsMetadataEqual(const Song& other) const {
|
|||||||
d->samplerate_ == other.d->samplerate_ &&
|
d->samplerate_ == other.d->samplerate_ &&
|
||||||
d->sampler_ == other.d->sampler_ &&
|
d->sampler_ == other.d->sampler_ &&
|
||||||
d->art_automatic_ == other.d->art_automatic_ &&
|
d->art_automatic_ == other.d->art_automatic_ &&
|
||||||
d->art_manual_ == other.d->art_manual_;
|
d->art_manual_ == other.d->art_manual_ &&
|
||||||
|
d->cue_path_ == other.d->cue_path_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Song::SetTextFrame(const QString& id, const QString& value,
|
void Song::SetTextFrame(const QString& id, const QString& value,
|
||||||
|
@ -171,6 +171,9 @@ class Song {
|
|||||||
int lastplayed() const { return d->lastplayed_; }
|
int lastplayed() const { return d->lastplayed_; }
|
||||||
int score() const { return d->score_; }
|
int score() const { return d->score_; }
|
||||||
|
|
||||||
|
const QString& cue_path() const { return d->cue_path_; }
|
||||||
|
bool has_cue() const { return !d->cue_path_.isEmpty(); }
|
||||||
|
|
||||||
int beginning() const { return d->beginning_; }
|
int beginning() const { return d->beginning_; }
|
||||||
int end() const { return d->end_; }
|
int end() const { return d->end_; }
|
||||||
|
|
||||||
@ -241,6 +244,7 @@ class Song {
|
|||||||
void set_skipcount(int v) { d->skipcount_ = v; }
|
void set_skipcount(int v) { d->skipcount_ = v; }
|
||||||
void set_lastplayed(int v) { d->lastplayed_ = v; }
|
void set_lastplayed(int v) { d->lastplayed_ = v; }
|
||||||
void set_score(int v) { d->score_ = qBound(0, v, 100); }
|
void set_score(int v) { d->score_ = qBound(0, v, 100); }
|
||||||
|
void set_cue_path(const QString& v) { d->cue_path_ = v; }
|
||||||
|
|
||||||
// Setters that should only be used by tests
|
// Setters that should only be used by tests
|
||||||
void set_filename(const QString& v) { d->filename_ = v; }
|
void set_filename(const QString& v) { d->filename_ = v; }
|
||||||
@ -312,6 +316,9 @@ class Song {
|
|||||||
int filesize_;
|
int filesize_;
|
||||||
FileType filetype_;
|
FileType filetype_;
|
||||||
|
|
||||||
|
// If the song has a CUE, this contains it's path.
|
||||||
|
QString cue_path_;
|
||||||
|
|
||||||
// Filenames to album art for this song.
|
// Filenames to album art for this song.
|
||||||
QString art_automatic_; // Guessed by LibraryWatcher
|
QString art_automatic_; // Guessed by LibraryWatcher
|
||||||
QString art_manual_; // Set by the user - should take priority
|
QString art_manual_; // Set by the user - should take priority
|
||||||
|
@ -574,6 +574,23 @@ Song LibraryBackend::GetSongByFilename(const QString& filename, int beginning) {
|
|||||||
return song;
|
return song;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SongList LibraryBackend::GetSongsByFilename(const QString& filename) {
|
||||||
|
LibraryQuery query;
|
||||||
|
query.SetColumnSpec("%songs_table.ROWID, " + Song::kColumnSpec);
|
||||||
|
query.AddWhere("filename", filename);
|
||||||
|
|
||||||
|
SongList songlist;
|
||||||
|
if (ExecQuery(&query)) {
|
||||||
|
while(query.Next()) {
|
||||||
|
Song song;
|
||||||
|
song.InitFromQuery(query);
|
||||||
|
|
||||||
|
songlist << song;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return songlist;
|
||||||
|
}
|
||||||
|
|
||||||
bool LibraryBackend::HasCompilations(const QueryOptions& opt) {
|
bool LibraryBackend::HasCompilations(const QueryOptions& opt) {
|
||||||
LibraryQuery query(opt);
|
LibraryQuery query(opt);
|
||||||
query.SetColumnSpec("%songs_table.ROWID");
|
query.SetColumnSpec("%songs_table.ROWID");
|
||||||
|
@ -82,6 +82,9 @@ class LibraryBackendInterface : public QObject {
|
|||||||
|
|
||||||
virtual Song GetSongById(int id) = 0;
|
virtual Song GetSongById(int id) = 0;
|
||||||
|
|
||||||
|
// Returns all sections of a song with the given filename. If there's just one section
|
||||||
|
// the resulting list will have it's size equal to 1.
|
||||||
|
virtual SongList GetSongsByFilename(const QString& filename) = 0;
|
||||||
// Returns a section of a song with the given filename and beginning. If the section
|
// Returns a section of a song with the given filename and beginning. If the section
|
||||||
// is not present in library, returns invalid song.
|
// is not present in library, returns invalid song.
|
||||||
// Using default beginning value is suitable when searching for single-section songs.
|
// Using default beginning value is suitable when searching for single-section songs.
|
||||||
@ -140,6 +143,7 @@ class LibraryBackend : public LibraryBackendInterface {
|
|||||||
SongList GetSongsByForeignId(const QStringList& ids, const QString& table,
|
SongList GetSongsByForeignId(const QStringList& ids, const QString& table,
|
||||||
const QString& column);
|
const QString& column);
|
||||||
|
|
||||||
|
SongList GetSongsByFilename(const QString& filename);
|
||||||
Song GetSongByFilename(const QString& filename, int beginning = 0);
|
Song GetSongByFilename(const QString& filename, int beginning = 0);
|
||||||
|
|
||||||
void AddDirectory(const QString& path);
|
void AddDirectory(const QString& path);
|
||||||
|
@ -26,9 +26,10 @@
|
|||||||
#include <QtDebug>
|
#include <QtDebug>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QTimer>
|
#include <QHash>
|
||||||
#include <QSettings>
|
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
#include <taglib/fileref.h>
|
#include <taglib/fileref.h>
|
||||||
#include <taglib/tag.h>
|
#include <taglib/tag.h>
|
||||||
@ -286,15 +287,13 @@ void LibraryWatcher::ScanSubdirectory(
|
|||||||
foreach (const QString& file, files_on_disk) {
|
foreach (const QString& file, files_on_disk) {
|
||||||
if (stop_requested_) return;
|
if (stop_requested_) return;
|
||||||
|
|
||||||
|
// associated cue
|
||||||
QString matching_cue = NoExtensionPart(file) + ".cue";
|
QString matching_cue = NoExtensionPart(file) + ".cue";
|
||||||
QDateTime cue_last_modified = QFileInfo(matching_cue).lastModified();
|
|
||||||
|
|
||||||
uint cue_mtime = cue_last_modified.isValid()
|
|
||||||
? cue_last_modified.toTime_t()
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
Song matching_song;
|
Song matching_song;
|
||||||
if (FindSongByPath(songs_in_db, file, &matching_song)) {
|
if (FindSongByPath(songs_in_db, file, &matching_song)) {
|
||||||
|
uint matching_cue_mtime = GetMtimeForCue(matching_cue);
|
||||||
|
|
||||||
// The song is in the database and still on disk.
|
// The song is in the database and still on disk.
|
||||||
// Check the mtime to see if it's been changed since it was added.
|
// Check the mtime to see if it's been changed since it was added.
|
||||||
QFileInfo file_info(file);
|
QFileInfo file_info(file);
|
||||||
@ -306,8 +305,16 @@ void LibraryWatcher::ScanSubdirectory(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cue sheet's path from library (if any)
|
||||||
|
QString song_cue = matching_song.cue_path();
|
||||||
|
uint song_cue_mtime = GetMtimeForCue(song_cue);
|
||||||
|
|
||||||
|
bool cue_deleted = song_cue_mtime == 0 && matching_song.has_cue();
|
||||||
|
bool cue_added = matching_cue_mtime != 0 && !matching_song.has_cue();
|
||||||
|
|
||||||
// watch out for cue songs which have their mtime equal to qMax(media_file_mtime, cue_sheet_mtime)
|
// watch out for cue songs which have their mtime equal to qMax(media_file_mtime, cue_sheet_mtime)
|
||||||
bool changed = matching_song.mtime() != qMax(file_info.lastModified().toTime_t(), cue_mtime);
|
bool changed = (matching_song.mtime() != qMax(file_info.lastModified().toTime_t(), song_cue_mtime))
|
||||||
|
|| cue_deleted || cue_added;
|
||||||
|
|
||||||
// Also want to look to see whether the album art has changed
|
// Also want to look to see whether the album art has changed
|
||||||
QString image = ImageForSong(file, album_art);
|
QString image = ImageForSong(file, album_art);
|
||||||
@ -317,71 +324,25 @@ void LibraryWatcher::ScanSubdirectory(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// the song's changed - reread the metadata from file
|
// the song's changed - reread the metadata from file
|
||||||
// TODO: problem if cue gets deleted or added before an update
|
|
||||||
if (changed) {
|
if (changed) {
|
||||||
qDebug() << file << "changed";
|
qDebug() << file << "changed";
|
||||||
|
|
||||||
// cue associated?
|
// if cue associated...
|
||||||
if(cue_mtime) {
|
if(!cue_deleted && (matching_song.has_cue() || cue_added)) {
|
||||||
QFile cue(matching_cue);
|
UpdateCueAssociatedSongs(file, path, matching_cue, image, t);
|
||||||
cue.open(QIODevice::ReadOnly);
|
// if no cue or it's about to lose it...
|
||||||
|
|
||||||
foreach(Song cue_song, cue_parser_->Load(&cue, matching_cue, path)) {
|
|
||||||
// update every song that's in the cue and library
|
|
||||||
Song matching_section = backend_->GetSongByFilename(cue_song.filename(), cue_song.beginning());
|
|
||||||
if(matching_section.is_valid()) {
|
|
||||||
cue_song.set_directory_id(t->dir());
|
|
||||||
PreserveUserSetData(file, image, matching_section, &cue_song, t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Song song_on_disk;
|
UpdateNonCueAssociatedSong(file, matching_song, image, cue_deleted, t);
|
||||||
song_on_disk.InitFromFile(file, t->dir());
|
|
||||||
|
|
||||||
if (!song_on_disk.is_valid())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
PreserveUserSetData(file, image, matching_song, &song_on_disk, t);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// The song is on disk but not in the DB
|
// The song is on disk but not in the DB
|
||||||
SongList song_list;
|
SongList song_list = ScanNewFile(file, path, matching_cue, &cues_processed);
|
||||||
|
|
||||||
// don't process the same cue many times
|
|
||||||
if(cues_processed.contains(matching_cue))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// it's a cue - create virtual tracks
|
|
||||||
if(cue_mtime) {
|
|
||||||
QFile cue(matching_cue);
|
|
||||||
cue.open(QIODevice::ReadOnly);
|
|
||||||
|
|
||||||
// ignore FILEs pointing to other media files
|
|
||||||
foreach(const Song& cue_song, cue_parser_->Load(&cue, matching_cue, path)) {
|
|
||||||
if(cue_song.filename() == file) {
|
|
||||||
song_list << cue_song;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(song_list.isEmpty()) {
|
if(song_list.isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
cues_processed << matching_cue;
|
|
||||||
|
|
||||||
// it's a normal media file
|
|
||||||
} else {
|
|
||||||
Song song;
|
|
||||||
song.InitFromFile(file, -1);
|
|
||||||
|
|
||||||
if (!song.is_valid()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
song_list << song;
|
|
||||||
}
|
|
||||||
|
|
||||||
qDebug() << file << "created";
|
qDebug() << file << "created";
|
||||||
// choose an image for the song(s)
|
// choose an image for the song(s)
|
||||||
QString image = ImageForSong(file, album_art);
|
QString image = ImageForSong(file, album_art);
|
||||||
@ -426,6 +387,108 @@ void LibraryWatcher::ScanSubdirectory(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LibraryWatcher::UpdateCueAssociatedSongs(const QString& file, const QString& path,
|
||||||
|
const QString& matching_cue, const QString& image,
|
||||||
|
ScanTransaction* t) {
|
||||||
|
QFile cue(matching_cue);
|
||||||
|
cue.open(QIODevice::ReadOnly);
|
||||||
|
|
||||||
|
SongList old_sections = backend_->GetSongsByFilename(file);
|
||||||
|
|
||||||
|
QHash<int, Song> sections_map;
|
||||||
|
foreach(const Song& song, old_sections) {
|
||||||
|
if(song.is_valid()) {
|
||||||
|
sections_map[song.beginning()] = song;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QSet<int> used_ids;
|
||||||
|
|
||||||
|
// update every song that's in the cue and library
|
||||||
|
foreach(Song cue_song, cue_parser_->Load(&cue, matching_cue, path)) {
|
||||||
|
cue_song.set_directory_id(t->dir());
|
||||||
|
|
||||||
|
Song matching = sections_map[cue_song.beginning()];
|
||||||
|
// a new section
|
||||||
|
if(!matching.is_valid()) {
|
||||||
|
t->new_songs << cue_song;
|
||||||
|
// changed section
|
||||||
|
} else {
|
||||||
|
PreserveUserSetData(file, image, matching, &cue_song, t);
|
||||||
|
used_ids.insert(matching.id());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sections that are now missing
|
||||||
|
foreach(const Song& matching, old_sections) {
|
||||||
|
if(!used_ids.contains(matching.id())) {
|
||||||
|
t->deleted_songs << matching;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void LibraryWatcher::UpdateNonCueAssociatedSong(const QString& file, const Song& matching_song,
|
||||||
|
const QString& image, bool cue_deleted,
|
||||||
|
ScanTransaction* t) {
|
||||||
|
// if a cue got deleted, we turn it's first section into the new
|
||||||
|
// 'raw' (cueless) song and we just remove the rest of the sections
|
||||||
|
// from the library
|
||||||
|
if(cue_deleted) {
|
||||||
|
foreach(const Song& song, backend_->GetSongsByFilename(file)) {
|
||||||
|
if(!song.IsMetadataEqual(matching_song) && song.is_valid()) {
|
||||||
|
t->deleted_songs << song;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Song song_on_disk;
|
||||||
|
song_on_disk.InitFromFile(file, t->dir());
|
||||||
|
|
||||||
|
if(song_on_disk.is_valid()) {
|
||||||
|
PreserveUserSetData(file, image, matching_song, &song_on_disk, t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SongList LibraryWatcher::ScanNewFile(const QString& file, const QString& path,
|
||||||
|
const QString& matching_cue, QSet<QString>* cues_processed) {
|
||||||
|
SongList song_list;
|
||||||
|
|
||||||
|
uint matching_cue_mtime = GetMtimeForCue(matching_cue);
|
||||||
|
// if it's a cue - create virtual tracks
|
||||||
|
if(matching_cue_mtime) {
|
||||||
|
// don't process the same cue many times
|
||||||
|
if(cues_processed->contains(matching_cue))
|
||||||
|
return song_list;
|
||||||
|
|
||||||
|
QFile cue(matching_cue);
|
||||||
|
cue.open(QIODevice::ReadOnly);
|
||||||
|
|
||||||
|
// ignore FILEs pointing to other media files
|
||||||
|
foreach(const Song& cue_song, cue_parser_->Load(&cue, matching_cue, path)) {
|
||||||
|
if(cue_song.filename() == file) {
|
||||||
|
song_list << cue_song;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!song_list.isEmpty()) {
|
||||||
|
*cues_processed << matching_cue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// it's a normal media file
|
||||||
|
} else {
|
||||||
|
Song song;
|
||||||
|
song.InitFromFile(file, -1);
|
||||||
|
|
||||||
|
if (song.is_valid()) {
|
||||||
|
song_list << song;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return song_list;
|
||||||
|
}
|
||||||
|
|
||||||
void LibraryWatcher::PreserveUserSetData(const QString& file, const QString& image,
|
void LibraryWatcher::PreserveUserSetData(const QString& file, const QString& image,
|
||||||
const Song& matching_song, Song* out, ScanTransaction* t) {
|
const Song& matching_song, Song* out, ScanTransaction* t) {
|
||||||
out->set_id(matching_song.id());
|
out->set_id(matching_song.id());
|
||||||
@ -449,6 +512,19 @@ void LibraryWatcher::PreserveUserSetData(const QString& file, const QString& ima
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint LibraryWatcher::GetMtimeForCue(const QString& cue_path) {
|
||||||
|
// slight optimisation
|
||||||
|
if(cue_path.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDateTime cue_last_modified = QFileInfo(cue_path).lastModified();
|
||||||
|
|
||||||
|
return cue_last_modified.isValid()
|
||||||
|
? cue_last_modified.toTime_t()
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
void LibraryWatcher::AddWatch(QFileSystemWatcher* w, const QString& path) {
|
void LibraryWatcher::AddWatch(QFileSystemWatcher* w, const QString& path) {
|
||||||
if (!QFile::exists(path))
|
if (!QFile::exists(path))
|
||||||
return;
|
return;
|
||||||
|
@ -133,8 +133,27 @@ class LibraryWatcher : public QObject {
|
|||||||
QString PickBestImage(const QStringList& images);
|
QString PickBestImage(const QStringList& images);
|
||||||
QString ImageForSong(const QString& path, QMap<QString, QStringList>& album_art);
|
QString ImageForSong(const QString& path, QMap<QString, QStringList>& album_art);
|
||||||
void AddWatch(QFileSystemWatcher* w, const QString& path);
|
void AddWatch(QFileSystemWatcher* w, const QString& path);
|
||||||
|
uint GetMtimeForCue(const QString& cue_path);
|
||||||
|
|
||||||
|
// Updates the sections of a cue associated and altered (according to mtime)
|
||||||
|
// media file during a scan.
|
||||||
|
void UpdateCueAssociatedSongs(const QString& file, const QString& path,
|
||||||
|
const QString& matching_cue, const QString& image,
|
||||||
|
ScanTransaction* t);
|
||||||
|
// Updates a single non-cue associated and altered (according to mtime) song
|
||||||
|
// during a scan.
|
||||||
|
void UpdateNonCueAssociatedSong(const QString& file, const Song& matching_song,
|
||||||
|
const QString& image, bool cue_deleted,
|
||||||
|
ScanTransaction* t) ;
|
||||||
|
// Updates a new song with some metadata taken from it's equivalent old
|
||||||
|
// song (for example rating and score).
|
||||||
void PreserveUserSetData(const QString& file, const QString& image,
|
void PreserveUserSetData(const QString& file, const QString& image,
|
||||||
const Song& matching_song, Song* out, ScanTransaction* t);
|
const Song& matching_song, Song* out, ScanTransaction* t);
|
||||||
|
// Scans a single media file that's present on the disk but not yet in the library.
|
||||||
|
// It may result in a multiple files added to the library when the media file
|
||||||
|
// has many sections (like a CUE related media file).
|
||||||
|
SongList ScanNewFile(const QString& file, const QString& path,
|
||||||
|
const QString& matching_cue, QSet<QString>* cues_processed);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// One of these gets stored for each Directory we're watching
|
// One of these gets stored for each Directory we're watching
|
||||||
|
@ -220,6 +220,7 @@ SongList CueParser::Load(QIODevice* device, const QString& playlist_path, const
|
|||||||
if(cue_mtime.isValid()) {
|
if(cue_mtime.isValid()) {
|
||||||
song.set_mtime(qMax(cue_mtime.toTime_t(), song.mtime()));
|
song.set_mtime(qMax(cue_mtime.toTime_t(), song.mtime()));
|
||||||
}
|
}
|
||||||
|
song.set_cue_path(playlist_path);
|
||||||
|
|
||||||
// overwrite the stuff, we may have read from the file or library, using
|
// overwrite the stuff, we may have read from the file or library, using
|
||||||
// the current .cue metadata
|
// the current .cue metadata
|
||||||
|
@ -56,6 +56,7 @@ public:
|
|||||||
SongList GetSongsByForeignId(const QStringList& ids, const QString& table,
|
SongList GetSongsByForeignId(const QStringList& ids, const QString& table,
|
||||||
const QString& column);
|
const QString& column);
|
||||||
|
|
||||||
|
SongList GetSongsByFilename(const QString& filename);
|
||||||
Song GetSongByFilename(const QString& filename, int beginning);
|
Song GetSongByFilename(const QString& filename, int beginning);
|
||||||
|
|
||||||
void AddDirectory(const QString& path);
|
void AddDirectory(const QString& path);
|
||||||
|
@ -55,6 +55,9 @@ public:
|
|||||||
int lastplayed() const;
|
int lastplayed() const;
|
||||||
int score() const;
|
int score() const;
|
||||||
|
|
||||||
|
const QString& cue_path() const;
|
||||||
|
bool has_cue() const;
|
||||||
|
|
||||||
int beginning() const;
|
int beginning() const;
|
||||||
int end() const;
|
int end() const;
|
||||||
|
|
||||||
@ -128,6 +131,7 @@ public:
|
|||||||
void set_filename(const QString& v);
|
void set_filename(const QString& v);
|
||||||
void set_basefilename(const QString& v);
|
void set_basefilename(const QString& v);
|
||||||
void set_directory_id(int v);
|
void set_directory_id(int v);
|
||||||
|
void set_cue_path(const QString& v);
|
||||||
|
|
||||||
// Comparison functions
|
// Comparison functions
|
||||||
bool IsMetadataEqual(const Song& other) const;
|
bool IsMetadataEqual(const Song& other) const;
|
||||||
|
@ -41,7 +41,7 @@ TEST_F(CueParserTest, ParsesASong) {
|
|||||||
QFile file(":testdata/onesong.cue");
|
QFile file(":testdata/onesong.cue");
|
||||||
file.open(QIODevice::ReadOnly);
|
file.open(QIODevice::ReadOnly);
|
||||||
|
|
||||||
SongList song_list = parser_.Load(&file, "", QDir(""));
|
SongList song_list = parser_.Load(&file, "CUEPATH", QDir(""));
|
||||||
|
|
||||||
// one song
|
// one song
|
||||||
ASSERT_EQ(1, song_list.size());
|
ASSERT_EQ(1, song_list.size());
|
||||||
@ -54,6 +54,7 @@ TEST_F(CueParserTest, ParsesASong) {
|
|||||||
ASSERT_EQ("", first_song.album());
|
ASSERT_EQ("", first_song.album());
|
||||||
ASSERT_EQ(1, first_song.beginning());
|
ASSERT_EQ(1, first_song.beginning());
|
||||||
ASSERT_EQ(1, first_song.track());
|
ASSERT_EQ(1, first_song.track());
|
||||||
|
ASSERT_EQ("CUEPATH", first_song.cue_path());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(CueParserTest, ParsesTwoSongs) {
|
TEST_F(CueParserTest, ParsesTwoSongs) {
|
||||||
@ -153,7 +154,7 @@ TEST_F(CueParserTest, AcceptsMultipleFileBasedCues) {
|
|||||||
QFile file(":testdata/manyfiles.cue");
|
QFile file(":testdata/manyfiles.cue");
|
||||||
file.open(QIODevice::ReadOnly);
|
file.open(QIODevice::ReadOnly);
|
||||||
|
|
||||||
SongList song_list = parser_.Load(&file, "", QDir(""));
|
SongList song_list = parser_.Load(&file, "CUEPATH", QDir(""));
|
||||||
|
|
||||||
// five songs
|
// five songs
|
||||||
ASSERT_EQ(5, song_list.size());
|
ASSERT_EQ(5, song_list.size());
|
||||||
@ -173,6 +174,7 @@ TEST_F(CueParserTest, AcceptsMultipleFileBasedCues) {
|
|||||||
ASSERT_EQ(1, first_song.beginning());
|
ASSERT_EQ(1, first_song.beginning());
|
||||||
ASSERT_EQ(second_song.beginning() - first_song.beginning(), first_song.length());
|
ASSERT_EQ(second_song.beginning() - first_song.beginning(), first_song.length());
|
||||||
ASSERT_EQ(-1, first_song.track());
|
ASSERT_EQ(-1, first_song.track());
|
||||||
|
ASSERT_EQ("CUEPATH", first_song.cue_path());
|
||||||
|
|
||||||
ASSERT_TRUE(second_song.filename().endsWith("files/longer_one.mp3"));
|
ASSERT_TRUE(second_song.filename().endsWith("files/longer_one.mp3"));
|
||||||
ASSERT_EQ("A1Song2", second_song.title());
|
ASSERT_EQ("A1Song2", second_song.title());
|
||||||
@ -190,6 +192,7 @@ TEST_F(CueParserTest, AcceptsMultipleFileBasedCues) {
|
|||||||
ASSERT_EQ(0, third_song.beginning());
|
ASSERT_EQ(0, third_song.beginning());
|
||||||
ASSERT_EQ(fourth_song.beginning() - third_song.beginning(), third_song.length());
|
ASSERT_EQ(fourth_song.beginning() - third_song.beginning(), third_song.length());
|
||||||
ASSERT_EQ(-1, third_song.track());
|
ASSERT_EQ(-1, third_song.track());
|
||||||
|
ASSERT_EQ("CUEPATH", third_song.cue_path());
|
||||||
|
|
||||||
ASSERT_TRUE(fourth_song.filename().endsWith("files/longer_two_p1.mp3"));
|
ASSERT_TRUE(fourth_song.filename().endsWith("files/longer_two_p1.mp3"));
|
||||||
ASSERT_EQ("A2P1Song2", fourth_song.title());
|
ASSERT_EQ("A2P1Song2", fourth_song.title());
|
||||||
@ -206,6 +209,7 @@ TEST_F(CueParserTest, AcceptsMultipleFileBasedCues) {
|
|||||||
ASSERT_EQ("Artist Two", fifth_song.albumartist());
|
ASSERT_EQ("Artist Two", fifth_song.albumartist());
|
||||||
ASSERT_EQ(1, fifth_song.beginning());
|
ASSERT_EQ(1, fifth_song.beginning());
|
||||||
ASSERT_EQ(-1, fifth_song.track());
|
ASSERT_EQ(-1, fifth_song.track());
|
||||||
|
ASSERT_EQ("CUEPATH", fifth_song.cue_path());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(CueParserTest, SkipsBrokenSongsInMultipleFileBasedCues) {
|
TEST_F(CueParserTest, SkipsBrokenSongsInMultipleFileBasedCues) {
|
||||||
|
@ -51,6 +51,7 @@ class MockLibraryBackend : public LibraryBackendInterface {
|
|||||||
|
|
||||||
MOCK_METHOD1(GetSongById, Song(int));
|
MOCK_METHOD1(GetSongById, Song(int));
|
||||||
|
|
||||||
|
MOCK_METHOD1(GetSongsByFilename, SongList(const QString&));
|
||||||
MOCK_METHOD2(GetSongByFilename, Song(const QString&, int));
|
MOCK_METHOD2(GetSongByFilename, Song(const QString&, int));
|
||||||
|
|
||||||
MOCK_METHOD1(AddDirectory, void(const QString&));
|
MOCK_METHOD1(AddDirectory, void(const QString&));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user