/*************************************************************************** yammimodel.cpp - description ------------------- begin : Sun Oct 7 2001 copyright : (C) 2001 by Brian O.Nlle email : yammi-developer@lists.sourceforge.net ***************************************************************************/ /*************************************************************************** * * * 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 "yammimodel.h" #include #include #include #include #include #include #include #include // TODO: avoid the following includes in yammimodel (because it is gui-stuff) #include #include #include "yammigui.h" #include "applytoalldialog.h" #include "options.h" #include "mediaplayer.h" #include "ConsistencyCheckParameter.h" #include "song.h" #include "songentry.h" #include "songentryint.h" #include "songentrystring.h" #include "songentrytimestamp.h" #include "songinfo.h" #include "fuzzsrch.h" #include "prefs.h" #include "mylist.h" #include "mydatetime.h" #include "util.h" YammiModel::YammiModel( YammiGui *y ) { m_yammi = y; } YammiModel::~YammiModel() {} Prefs* YammiModel::config() { return &m_config; } /** * Reads from an xml file all existing songs into a given folder. * Not existing songs will be forgotten. * Creates a new category, if no folder given. */ bool YammiModel::readList(MyList* list, QString filename) { QFile f(filename); if ( !f.open( IO_ReadOnly ) ) { kdError() << "Could not open file for reading:" << filename << endl; return false; } QString errorMsg; int errorLine; int errorColumn; QDomDocument doc; if( !doc.setContent(&f, false, &errorMsg, &errorLine, &errorColumn) ) { QString msg = QString(i18n("Error reading categories file:\n%1\n(Error: %2, line %3, column %4)") ).arg(f.name()).arg(errorMsg).arg(errorLine).arg(errorColumn); kdError() << "Error reading file contents: " << filename << endl << msg << endl; f.close(); return false; } f.close(); // get root element QDomElement e = doc.documentElement(); if(e.tagName()!="category") { kdError() << "Does not seem to be a category xml file: " << filename << endl; return false; } QString name = e.attribute("name"); if(list == 0) { // add category to database list = new MyList; allCategories.append(list); categoryNames.append(name); } // add all songs contained e = e.firstChild().toElement( ); int index=1; while( !e.isNull() ) { QString artist = e.attribute("artist"); QString title = e.attribute("title"); QString album = e.attribute("album"); // search for item in allSongs Song* s = allSongs.getSongByKey(artist, title, album); if(!s) { kdWarning() << "Song not found in database : " << artist << "/" << title << "/" << album << endl; } else { SongEntryInt* toAdd = new SongEntryInt(s, index); list->append(toAdd); index++; } e = e.nextSibling().toElement(); } return true; } /** * Reads in all xml-files found in the database directory. * Each xml file should represent one category. */ void YammiModel::readCategories() { QString categoryDir(config()->databaseDir + "categories"); kdDebug() << "Reading categories from " << categoryDir << endl; int count=0; QDir d(categoryDir); if(!d.exists()) { kdDebug() << "could not read categories: directory not existing: " << categoryDir << endl; categoriesChanged(false); return; } QString filter("*.xml"); QStringList cats = d.entryList(filter, QDir::Files, QDir::DirsFirst); int total=cats.count(); KProgressDialog dia(0,0,i18n("Loading categories"),i18n("Reading categories..."), true); dia.setAllowCancel(false); dia.setMinimumDuration(0); KProgress *p = dia.progressBar(); p->setTotalSteps( total ); int progressCount=0; for( QStringList::Iterator it = cats.begin(); it!=cats.end(); ++it, progressCount++ ) { p->setProgress( progressCount ); kapp->eventLoop()->processEvents(QEventLoop::ExcludeUserInput); QString filename(d.absPath()+ "/" + (*it)); kdDebug() << "category found: " << filename << endl; if(readList(0, filename)) { count++; } } kdDebug() << count << " categories read" << endl; categoriesChanged(false); } // Reads song history void YammiModel::readHistory() { QString filename = config()->databaseDir + "history.xml"; kdDebug() << "reading song history from " << filename << endl; QFile f( filename ); if ( !f.open( IO_ReadOnly ) ) { kdError() << "could not open history file : " << filename << endl; return; } QDomDocument doc; if ( !doc.setContent( &f ) ) { kdError() << "could not parse history file, incorrect xml format? " << filename << endl; f.close(); return; } f.close(); QDomElement root = doc.documentElement(); int total = root.attribute("count", "0").toInt( ); QDomElement e = root.firstChild().toElement(); KProgressDialog dia(0,0,i18n("Loading song history"),i18n("Reading song history..."), true); dia.setAllowCancel(false); dia.setMinimumDuration(0); KProgress *p = dia.progressBar(); p->setTotalSteps( total ); for(int i=0; !e.isNull(); i++) { if(i % 100==0) { p->setProgress( i ); kapp->eventLoop()->processEvents(QEventLoop::ExcludeUserInput); } QString artist = e.attribute("artist", "unknown"); QString title = e.attribute("title", "unknown"); QString album = e.attribute("album", ""); QString timestamp = e.attribute("timestamp", ""); // search for item in allSongs Song* s = allSongs.getSongByKey(artist, title, album); if(!s) { kdWarning()<<"history item "<lastPlayed=played; SongEntryTimestamp* hEntry=new SongEntryTimestamp(s, &played); songHistory.append(hEntry); } e = e.nextSibling().toElement(); } kdDebug()<<"done (" << songHistory.count() << " songs in history)"<databaseDir; kdDebug() << "Saving history file in directory" << path << endl; QDomDocument doc; doc.appendChild( doc.createProcessingInstruction( "xml", "version=\"1.0\" encoding=\"UTF-8\"" ) ); QDomElement root = doc.createElement("history"); doc.appendChild(root); // iterate through songs in history AND songsPlayed folder // => save each song as a xml song element int count=0; for(SongEntry* entry=songHistory.first(); entry; entry=songHistory.next()) { QDomElement elem = doc.createElement( "song" ); elem.setAttribute( "artist", entry->song()->artist ); elem.setAttribute( "title", entry->song()->title ); elem.setAttribute( "album", entry->song()->album ); elem.setAttribute( "timestamp", ((SongEntryTimestamp*)entry)->timestamp.writeToString() ); root.appendChild( elem ); count++; } for(SongEntry* entry=songsPlayed.first(); entry; entry=songsPlayed.next()) { QDomElement elem = doc.createElement( "song" ); elem.setAttribute( "artist", entry->song()->artist ); elem.setAttribute( "title", entry->song()->title ); elem.setAttribute( "album", entry->song()->album ); elem.setAttribute( "timestamp", ((SongEntryTimestamp*)entry)->timestamp.writeToString() ); root.appendChild( elem ); count++; } root.setAttribute("count",count); // save history to file... (but first we make a backup of old file) QDir dir(path); if(dir.exists("history.xml") && dir.rename( path + "history.xml", path +"history_backup.xml")) { kdDebug() << "backup of history saved in \"history_backup.xml\"" << endl; } QString filename(path + "history.xml"); QFile file(filename); if(!file.open(IO_WriteOnly)) { kdError() << "could not open file for writing:" << filename << endl; return; } QTextStream str(&file); str.setEncoding(QTextStream::UnicodeUTF8); doc.save(str, 2); file.close(); kdDebug() << "done" << endl; } /// save categories (if changed) to xml-files void YammiModel::saveCategories() { QString path = config()->databaseDir + "categories/"; QDir dir(path); if(!dir.exists()) { kdDebug() << "creating directory " << path << endl; dir.mkdir(path, true); } kdDebug() << "saving dirty categories to directory " << path << endl; // save all categories marked as dirty QString categoryName; int index=0; for(MyList* category=allCategories.first(); category; category=allCategories.next(), index++) { categoryName = categoryNames[index]; kdDebug() << "category " << categoryName << endl; if(!category->dirty) { kdDebug() << "...clean\n"; continue; } kdDebug() << "...dirty, saving..." << endl; saveList(category, path, categoryName); category->dirty=false; } categoriesChanged(false); } bool YammiModel::saveList(MyList* list, QString path, QString filename) { QDomDocument doc; doc.appendChild( doc.createProcessingInstruction( "xml", "version=\"1.0\" encoding=\"UTF-8\"" ) ); QDomElement root = doc.createElement("category"); root.setAttribute("name", filename); doc.appendChild(root); for(Song* s = list->firstSong(); s; s=list->nextSong()) { QDomElement elem = doc.createElement( "song" ); elem.setAttribute( "artist", s->artist ); elem.setAttribute( "title", s->title ); elem.setAttribute( "album", s->album ); root.appendChild( elem ); } // save to file... QString absoluteFilename(path + "/" + filename + ".xml"); QFile file(absoluteFilename); if(!file.open(IO_WriteOnly)) { kdError() << "Could not save folder " << absoluteFilename << endl; return false; } QTextStream str(&file); str.setEncoding(QTextStream::UnicodeUTF8); doc.save(str, 2); file.close(); return true; } // reads the yammi database (an xml-file with all song information) void YammiModel::readSongDatabase( ) { QString filename(config()->databaseDir + "songdb.xml"); kdDebug() << "reading song database from: " << filename << endl; QFile f(filename); if( !f.open(IO_ReadOnly) ) { kdWarning() << "could not read song database from " << filename << endl; return; } QDomDocument doc; QString errorMsg; int errorLine; int errorColumn; if( !doc.setContent(&f, false, &errorMsg, &errorLine, &errorColumn) ) { f.close(); QString msg = QString(i18n("Error reading database file:\n%1\n(Error: %2, line %3, column %4)") ).arg(f.name()).arg(errorMsg).arg(errorLine).arg(errorColumn); KMessageBox::error(0L,msg,i18n("Error reading database")); return; } f.close(); QDomElement root = doc.documentElement( ); QString version = root.attribute("yammiVersion", "no version"); if(version=="0.5.2" || version=="0.5.1" || version=="0.5" || version=="no version") { QString msg( i18n("Your Song Database seems to be very old.\n You might need to create \ a new Database and scan your harddisk for songs") ); KMessageBox::sorry( 0L, msg, i18n("Unknown Song Database version") ); //try to continue anyway...? } int total = root.attribute("count", "0").toInt( ); QDomElement e = root.firstChild().toElement(); KProgressDialog dia(0,0,i18n("Loading database"),i18n("Reading Song Database file"), true); dia.setAllowCancel(false); dia.setMinimumDuration(0); KProgress *p = dia.progressBar(); p->setTotalSteps( total ); int count = 0; while( !e.isNull( ) ) { if(count % 100==0) { p->setProgress( count ); kapp->eventLoop()->processEvents(QEventLoop::ExcludeUserInput); } QString artist = e.attribute("artist", "unknown"); QString title = e.attribute("title", "unknown"); QString album = e.attribute("album", ""); QString filename = e.attribute("filename", ""); QString path = e.attribute("path", ""); QString comment = e.attribute("comment", ""); QString genre = e.attribute("genre", ""); unsigned long length = e.attribute("length","0").toULong( ); int bitrate = e.attribute("bitrate", "0").toInt( ); int year = e.attribute("year", "0").toInt( ); int trackNr = e.attribute("trackNr", "0").toInt( ); // read date as "dd/mm/yyyy, hh:mm:ss" MyDateTime addedTo; addedTo.readFromString(e.attribute("addedTo", "1/1/1970/00:00:00")); Song* s = new Song(artist, title, album, filename, path, length, bitrate, addedTo, year, comment, trackNr, genre); allSongs.appendSong( s ); QDomNode child = e.firstChild(); while( !child.isNull() ) { QDomElement mediaElem = child.toElement(); // try to convert the node to an element. if( !mediaElem.isNull() ) { QString mediaName=mediaElem.attribute("mediaName", "unspecified media"); QString mediaLocation=mediaElem.attribute("mediaLocation", "unspecified location"); s->addMediaLocation(mediaName, mediaLocation); } child = child.nextSibling(); } e = e.nextSibling().toElement(); count++; } kdDebug() << "Read " << count << " songs from database" << endl; allSongsChanged( false ); } /// saves the songs in allSongs into an xml-file void YammiModel::saveSongDatabase() { kdDebug() << "saving database..." << endl; QDomDocument doc; doc.appendChild( doc.createProcessingInstruction( "xml", "version=\"1.0\" encoding=\"UTF-8\"" ) ); QDomElement root = doc.createElement("songs"); root.setAttribute("yammiVersion", m_yammi->config()->yammiVersion); doc.appendChild(root); // iterate through songs and save each song as a xml song element int count=0; for(Song* s = allSongs.firstSong(); s; s = allSongs.nextSong()) { count++; QDomElement elem = doc.createElement( "song" ); if(s->artist!="unknown") elem.setAttribute( "artist", s->artist ); if(s->title!="unknown") elem.setAttribute( "title", s->title ); if(s->album!="") elem.setAttribute( "album", s->album); if(s->bitrate!=0) elem.setAttribute( "bitrate", QString("%1").arg(s->bitrate)); if(s->comment!="") elem.setAttribute( "comment", s->comment ); if(s->filename!="") elem.setAttribute( "filename", s->filename ); if(s->length!=0) elem.setAttribute( "length", QString("%1").arg(s->length)); if(s->path!="") elem.setAttribute( "path", s->path); if(s->year!=0) elem.setAttribute( "year", QString("%1").arg(s->year) ); if(s->trackNr!=0) elem.setAttribute( "trackNr", QString("%1").arg(s->trackNr)); if(s->genre!="") elem.setAttribute( "genre", QString("%1").arg(s->genre)); elem.setAttribute( "addedTo", s->addedTo.writeToString()); for(unsigned int i=0; imediaName.count(); i++) { QDomElement media = doc.createElement( "media" ); media.setAttribute( "mediaName", s->mediaName[i]); media.setAttribute( "mediaLocation", s->mediaLocation[i]); elem.appendChild(media); } root.appendChild( elem ); } root.setAttribute("count",count); // save songdb to file... (but first we make a backup of old file) QString filename = config()->databaseDir + "songdb.xml"; kdDebug() << "Saving song database to file " << filename << endl; QDir dir(config()->databaseDir); if(dir.exists("songdb.xml") && !dir.rename(filename, filename + "_backup")) { kdDebug() << "could not make backup of file " << filename << endl; } QFile file(filename); if(!file.open(IO_WriteOnly)) { kdError() << "could not open file for writing:" << filename << endl; return; } QTextStream str(&file); str.setEncoding(QTextStream::UnicodeUTF8); doc.save(str, 2); file.close(); kdDebug() << "saving database: done" << endl; allSongsChanged(false); } void YammiModel::allSongsChanged(bool changed) { _allSongsChanged = changed; // if(m_yammi && m_yammi->tbSaveDatabase) // m_yammi->tbSaveDatabase->setEnabled(_allSongsChanged || _categoriesChanged); } bool YammiModel::allSongsChanged() { return _allSongsChanged; } void YammiModel::categoriesChanged(bool changed) { _categoriesChanged = changed; // if(m_yammi && m_yammi->tbSaveDatabase) // m_yammi->tbSaveDatabase->setEnabled(_allSongsChanged || _categoriesChanged); } bool YammiModel::categoriesChanged() { return _categoriesChanged; } // Updates the xml-database by scanning harddisk void YammiModel::updateSongDatabase(QString scanDir, bool followSymLinks, QString filePattern, QString mediaName, KProgressDialog* progress) { entriesAdded=0; corruptSongs=0; if(m_yammi->config()->childSafe) { kdWarning()<<"updateSongDatabase : childSafe --> request denied"<config()->mountMediaDir) { // linux specific QString cmd; cmd=QString("mount %1").arg(scanDir); system(cmd); } // check that mediaDir is an existing directory QDir d(scanDir); if(!d.exists()) { QString msg(i18n("The specified directory for removable media:\n %1 \n\ does not exist or is not redable!\n Set value \"mediaDir\" in the preferences to an existing directory!\n\ (if necessary, enable the \"mount media\" option in the preferences)")); msg.arg(scanDir); KMessageBox::error( m_yammi, msg); } else { traverse(scanDir, followSymLinks, filePattern, progress, mediaName); kdDebug() << "finished scanning!" << endl; } // umount media dir if(m_yammi->config()->mountMediaDir) { // linux specific QString cmd; cmd=QString("umount %1").arg(scanDir); system(cmd); } } } void YammiModel::updateSongDatabase(QStringList list) { entriesAdded=0; corruptSongs=0; if(m_yammi->config()->childSafe) { kdWarning()<<"updateSongDatabase : childSafe --> request denied"<begin();it2 != playlist->end();++it) { addSongToDatabase(QString(*it2), 0); } delete playlist; } else { addSongToDatabase(filename, 0); } } } /// traverses a directory recursively and processes all mp3 files /// returns false, if scanning was cancelled bool YammiModel::traverse(QString path, bool followSymLinks, QString filePattern, KProgressDialog* progress, QString mediaName) { // leave out the following directories if(path+"/"==m_yammi->config()->trashDir || path+"/"==m_yammi->config()->swapDir) { kdWarning() << "skipping trash or swap directory: " << path << endl; return true; } kdDebug() << "scanning directory " << path<setLabel(QString(i18n("scanning directory %1 ...")).arg(path)); progress->progressBar()->setProgress(0); kapp->processEvents(); QDir d(path); // step 1: scan files d.setFilter(QDir::Files); d.setNameFilter(filePattern); d.setSorting( QDir::Name ); const QFileInfoList* list = d.entryInfoList(); if (!list) { kdWarning() << "Warning: Skipping unreadable directory: " << path << "\n"; return true; } int filesToScan=list->count(); progress->progressBar()->setTotalSteps(filesToScan); QFileInfoListIterator it( *list ); int filesScanned=0; for(QFileInfo *fi; (fi=it.current()); ++it ) { filesScanned++; progress->progressBar()->setProgress(filesScanned); if(progress->wasCancelled()) { return false; } // okay, we have a file to scan, try to add to database addSongToDatabase(fi->filePath(), mediaName); } // step 2: recursively scan subdirectories QDir d2(path); d2.setFilter(QDir::Dirs | QDir::Readable); d2.setSorting( QDir::Name ); const QFileInfoList* list2 = d2.entryInfoList(); if (!list2) { kdWarning() << "Warning: Skipping unreadable child directory: " << path << "\n"; return true; } QFileInfoListIterator it2( *list2 ); for(QFileInfo *fi2; (fi2=it2.current()); ++it2 ) { if(fi2->fileName()=="." || fi2->fileName()=="..") { continue; } if(followSymLinks == false) { if(fi2->isSymLink()) { kdDebug() << "skipping symlink " << fi2->filePath() << endl; continue; } } if(traverse(fi2->filePath(), followSymLinks, filePattern, progress, mediaName) == false) { return false; } } return true; } /** * fix incorrect genres by re-reading them from whole song database */ void YammiModel::fixGenres(KProgressDialog* progress) { int i = 0; for(Song* s=allSongs.firstSong(); s; s=allSongs.nextSong(), i++) { progress->progressBar()->setProgress(i); kapp->processEvents(); kapp->eventLoop()->processEvents(QEventLoop::ExcludeUserInput); if(progress->wasCancelled()) { kdDebug() << "fixing genres aborted..." << endl; return; } kdDebug() << "trying to fix genre in file " << s->location() << endl; Song* fixSong=new Song(); bool success = fixSong->create(s->location(), 0, config()->capitalizeTags); if(success) { kdDebug() << "old genre saved in yammi database: " << s->genre << ", fixed genre: " << fixSong->genre << endl; s->genre = fixSong->genre; allSongsChanged(true); } delete(fixSong); } } // adds a single songfile to the database void YammiModel::addSongToDatabase(QString filename, QString mediaName=0) { kdDebug() << "scanning file '" << filename << "'..." << endl; bool found=false; for(Song* s=allSongs.firstSong(); s; s=allSongs.nextSong()) { // this check might fail when filename has strange characters? if(filename == s->location()) { found=true; /* // here we can fix/update our database with additional info... // eg: fix genre like this: if(s->genre == "") { kdDebug() << "trying to fix genre in file " << filename << endl; Song* fixSong=new Song(); bool success = fixSong->create(filename, "", config()->capitalizeTags); if(success) { kdDebug() << "old genre: " << s->genre << ", fixed genre: " << fixSong->genre << endl; s->genre = fixSong->genre; allSongsChanged(true); } delete(fixSong); } */ break; } } if(found) { return; } // okay, new song (at least new filename/path) => construct song object Song* newSong = new Song(); newSong->create(filename, mediaName, m_yammi->config()->capitalizeTags); if(newSong->corrupted) { kdError() << "new song file " << filename << " is corrupt (not readable for yammi), skipping" << endl; corruptSongs++; return; } // check whether other version of this song existing (with same primary key) Song* s = allSongs.getSongByKey(newSong->artist, newSong->title, newSong->album); if(s!=0) { // yes, song with this key already existing! if(newSong->length==s->length && newSong->bitrate==s->bitrate && newSong->album==s->album) { // a) okay, we assume it is exactly the same song... if(mediaName==0) { // case 1: scanning harddisk if(s->filename=="") { // case 1a: song has no filename = was not available on harddisk // => make it available (at new location) s->setTo(newSong); allSongsChanged(true); delete(newSong); return; } else { // case 1b: song has filename QFileInfo fileInfo(s->location()); if(!fileInfo.exists()) { // but still not available => probably has been moved kdWarning() << "looks like file " << newSong->filename << " has been moved from " << s->path << " to " << newSong->path << ", correcting path info" << endl; s->setTo(newSong); allSongsChanged(true); delete(newSong); return; } // song is available, we don't need two songs => skip kdWarning() << "file " << newSong->filename << " already available at " << s->location() << ", skipping" << endl; delete(newSong); return; } } else { // case 2: scanning removable media => add media, if not already added bool exists=false; for(unsigned int i=0; imediaName.count(); i++) { if(s->mediaName[i]==mediaName) exists=true; } if(!exists) { kdDebug() << "adding media " << mediaName << " to mediaList in song " << s->displayName() << endl; QString locationOnMedia=filename; if(locationOnMedia.left(m_yammi->config()->mediaDir.length())!=m_yammi->config()->mediaDir) kdError() << "strange error, scanning media, but file not on media" << endl; locationOnMedia=locationOnMedia.right(locationOnMedia.length()-m_yammi->config()->mediaDir.length()); s->addMediaLocation(mediaName, locationOnMedia); allSongsChanged(true); } else { kdDebug() << "song " << s->location() << " is already known to be on this media" << endl; } return; } } else { // b) not exactly the same => add as new (change title to make it unique) // here we do not have to distinguish between harddisk and media kdWarning() << "seems like new song '" << newSong->artist << " - " << newSong->title << "' already existing..." << endl << "(Yammi does not allow two songs with the same artist/title/album identification)" << endl; int tryNo=2; QString extTitle; for(bool notUnique=true; notUnique; tryNo++) { extTitle=newSong->title+QString("(%1)").arg(tryNo); notUnique=(allSongs.getSongByKey(s->artist, extTitle, s->album)!=0); } newSong->title=extTitle; problematicSongs.append(new SongEntryString(newSong, QString("song existing, appended with (%1)").arg(tryNo-1))); problematicSongs.append(new SongEntryString(s, "existing song")); } } // new song, not in database yet allSongs.appendSong(newSong); kdDebug() << "Song added to database: " << newSong->displayName() << endl; entriesAdded++; allSongsChanged(true); } /** * Read an m3u file, returning a list of strings */ QStringList* YammiModel::readM3uFile(QString filename) { QStringList* list=new QStringList(); QFile file(filename); if (!file.open( IO_ReadOnly ) ) return list; QTextStream stream(&file); while(!stream.atEnd()) { QString line=stream.readLine().stripWhiteSpace(); if(line.startsWith("#")) { // skip lines starting with # continue; } list->append(line); } file.close(); return list; } void YammiModel::removeCategory(QString categoryName) { kdDebug() << "remove category: " << categoryName << endl; QString name=categoryNames.first(); int i=0; for(MyList* ptr=allCategories.first(); ptr; ptr=allCategories.next(), i++) { QString name=categoryNames[i]; if(name==categoryName) { kdDebug() << "category found, deleting..." << endl; allCategories.remove(); categoryNames.remove(categoryNames.at(i)); QString file = config()->databaseDir + "categories/" + name + ".xml"; kdDebug() << "file to delete:" << file << endl; QDir d; if(!d.remove(file)) { kdWarning() << "could not delete a category file: " << file << endl; } break; } } } void YammiModel::renameCategory(QString oldCategoryName, QString newCategoryName) { int i=0; for(MyList* ptr=allCategories.first(); ptr; ptr=allCategories.next(), i++) { QString name=categoryNames[i]; if(name==oldCategoryName) { kdDebug() << "renaming category.." << endl; categoryNames[i]=newCategoryName; ptr->dirty=true; categoriesChanged(true); QDir dir; QString path = config()->databaseDir + "categories/"; if(!dir.rename(path+oldCategoryName+".xml", path+newCategoryName+".xml")) kdError() << "could not rename category file:" << path << oldCategoryName << ".xml" << endl; break; } } } void YammiModel::newCategory(QString name) { MyList* newList = new MyList; allCategories.append(newList); categoryNames.append(name); newList->dirty=true; categoriesChanged(true); } /** save all * (needed for update / correcting xml-file format) * marks everything as dirty and invokes save() */ void YammiModel::saveAll() { categoriesChanged(true); allSongsChanged(true); int i=0; for(MyList* ptr=allCategories.first(); ptr; ptr=allCategories.next(), i++) { ptr->dirty=true; } save(); } /** * saves changed information (categories + songDatabase) */ void YammiModel::save() { kdDebug() << "model save()" << endl; KApplication::setOverrideCursor( Qt::waitCursor ); // save dirty categories saveCategories(); if(allSongsChanged()) { saveSongDatabase(); } if(allSongsChanged() || m_yammi->config()->logging) { saveHistory(); } KApplication::restoreOverrideCursor(); } /* removing a media, ie. removing the media entry in all songs that are on it */ void YammiModel::removeMedia(QString mediaToDelete) { kdDebug()<<"removing media: "<mediaLocation.begin(); for ( QStringList::Iterator it = s->mediaName.begin(); it != s->mediaName.end(); ++it ) { if(mediaToDelete==(*it)) { it=s->mediaName.remove(it); --it; it2=s->mediaLocation.remove(it2); --it2; } ++it2; } } // now delete the directory (if existing) // linux specific kdWarning()<<"Remove media disabled"<config()->yammiBaseDir).arg(mediaToDelete); // system(cmd); allSongsChanged(true); } void YammiModel::renameMedia(QString oldMediaName, QString newMediaName) { for(Song* s=allSongs.firstSong(); s; s=allSongs.nextSong()) { s->renameMedia(oldMediaName, newMediaName); } // now move the directory (if existing) QDir dir; QString path = config()->databaseDir + "media/"; if(!dir.rename(path+oldMediaName, path+newMediaName)) { kdDebug() << "could not rename media dir!:" << path + oldMediaName << endl; } allSongsChanged(true); } /** * Marks those playlists as dirty that contain the given song */ void YammiModel::markPlaylists(Song* s) { for(MyList* category=allCategories.first(); category; category=allCategories.next()) { if(category->containsSong(s)) { category->dirty=true; } } } // finds out the corresponding song entry given a filename // (now also takes care of songs in swap dir) // returns 0 if no song entry found Song* YammiModel::getSongFromFilename(QString filename) { // strip filename to relative name int pos=filename.findRev('/', -1); QString path=filename.left(pos+1); QString lookFor=filename.right(filename.length()-pos-1); if(path==m_yammi->config()->swapDir) { for(SongEntry* entry=allSongs.first(); entry; entry=allSongs.next()) { if(entry->song()->filename=="" && entry->song()->constructFilename()==lookFor) return entry->song(); } } else { for(SongEntry* entry=allSongs.first(); entry; entry=allSongs.next()) { if(entry->song()->filename==lookFor) return entry->song(); } } return 0; } /** * Checks whether a song is available on the local harddisk * or needs to be retrieved from a removable media. * If song is available, returns the absolute path+filename to the songfile. * (if song is in swap dir and touch is set to true, * the file will be touched to implement a kind of LRU strategy) * If not yet available, returns "". * If song will never be available because it is not contained on any media, returns "never". */ QString YammiModel::checkAvailability(Song* s, bool touch) { if(s->location()!="/") { QFileInfo fi(s->location()); if(fi.exists() && fi.isReadable()) { return s->location(); } } // no location given, check whether already existing in swap dir QString dir=m_yammi->config()->swapDir; QString filename=s->constructFilename(); QFileInfo fi(dir+filename); if(fi.exists() && fi.isReadable()) { if(touch) { // linux specific QString cmd; cmd=QString("touch \"%1\"").arg(dir+filename); system(cmd); /* does not work: touching a file using QT classes... QFile touchFile(dir+filename); if(!touchFile.open(IO_ReadWrite)) cout << "could not touch songfile (for LRU method)\n"; else { touchFile.flush(); touchFile.close(); } */ } return dir+filename; } // not available, need to load it from media if(s->mediaLocation.count()!=0) { return ""; } else { return "never"; } } /** * Remove all those thongs from top of playlist that are unplayable: * - swapped songs not on harddisk and not loaded from removable media * - those that match unplayable file mask ("*.txt") * Does NOT emit the playlistChanged() signal! */ bool YammiModel::skipUnplayableSongs(bool firstTwo) { kdDebug() << "skipUnplayableSongs()\n"; QString location; bool songsRemoved = false; while( songsToPlay.at(0) ) { location = checkAvailability( songsToPlay.at(0)->song() ); if( location == "" || location == "never" ) { kdDebug() << "Song " << songsToPlay.at(0)->song()->displayName() << "not available, skipping\n"; songsToPlay.removeFirst(); songsRemoved = true; } else { break; } } if(firstTwo) { while( songsToPlay.at(1) ) { location = checkAvailability( songsToPlay.at(1)->song() ); if( location == "" || location == "never" ) { kdDebug() << "Song " << songsToPlay.at(1)->song()->displayName() << "not available, skipping\n"; songsToPlay.remove(1); songsRemoved = true; } else { break; } } } return songsRemoved; }