/* * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * NOTES: * * Need a way to update the song credits in format 2 playlists when * the sid file has been updated. One way to achieve this could be * to let the player do the comparison of credits found in playlist * and sid file and provide the playlist with an updated entry. */ #ifdef XSID_WB_DEBUG #include #endif #include #include #include using namespace std; #include #include "Playlist.h" #include "wrapper/SidTuneWrapper.h" const char* const Playlist::keys[] = { "[SIDPLAY/Windows Playlist]", "Format", "Basedir", // Format 1 "Filename", "Subsong", // Format 1 & 2 "time", // Format 1: "Time" // but we do case-insensitive comparison // Format 2 "file", "name", "author", "copyright", "songs", "subtune", "fadeout" }; Playlist::Playlist() : TextFile(), baseDir(""), saveFormat(2) { list.setAutoDelete(true); clear(); } void Playlist::lock() { listMutex.lock(); } void Playlist::unlock() { listMutex.unlock(); } void Playlist::setModified() { modified = true; } bool Playlist::isModified() const { return modified; } uint Playlist::getCurrentPlayPos() const { return currentPlayPos; } void Playlist::clear() { lock(); list.clear(); currentPlayPos = 0; modified = false; // no one would want to save an empty list unlock(); } void Playlist::add(PlaylistItem* item) { lock(); list.append(item); modified = true; unlock(); } void Playlist::remove(uint pos) { lock(); list.remove(pos); modified = true; // Adjust current play position if necessary, so next // item to be played is correct. if (currentPlayPos >= pos) --currentPlayPos; unlock(); } void Playlist::setCurrentPlayPos(uint pos) { lock(); if ( pos>=0 && pos it(list); for ( it.toFirst(); it.current(); ++it ) { PlaylistItem* item = it.current(); item->fileNameString.prepend(tmpBaseDir); } unlock(); } // Try to find a common 'baseDir' path prefix by examining all // file names in the playlist and modifying them if necessary. // If the maxBasePrefix argument is a sub-string of the found // prefix, return maxBasePrefix (=> HVSC root hack). void Playlist::findBaseDir() { if ( list.isEmpty() ) return; lock(); QListIterator it(list); QString tmpBase = it.toFirst()->fileNameString; ++it; // Determine a first path to start with. int lastSlashPos = tmpBase.findRev( QDir::separator() ); if (lastSlashPos < 0) lastSlashPos = 0; // slash only = no common path prefix tmpBase.truncate(lastSlashPos); if ( !tmpBase.isEmpty() ) { // Now tmpBase is the string we try to find at the start // of each other file name. If we find it, we may be able // to determine a common path prefix. If we cannot find // it, we truncate the search string until nothing is left. for ( ; it.current(); ++it ) { QString& s = it.current()->fileNameString; while ( !s.startsWith(tmpBase) && !tmpBase.isEmpty() ) { // Cut off one directory name. lastSlashPos = tmpBase.findRev( QDir::separator() ); if (lastSlashPos < 0) lastSlashPos = 0; tmpBase.truncate(lastSlashPos); }; } // Prefix length hack. if ( tmpBase.startsWith(maxBasePrefix) ) { tmpBase = maxBasePrefix; } // Now remove the common path prefix from every file name. if ( !tmpBase.isEmpty() ) { for ( it.toFirst(); it.current(); ++it ) { it.current()->fileNameString.remove(0,tmpBase.length() ); } } if ( baseDir!=tmpBase ) { modified = true; baseDir = tmpBase; } } unlock(); } void Playlist::fixMissingInfo() { if ( list.isEmpty() ) return; lock(); SidTuneWrapper* mySidLoader = new SidTuneWrapper; // unchecked alloc QListIterator it(list); for ( it.toFirst(); it.current(); ++it ) { PlaylistItem* item = it.current(); if ( mySidLoader->open(item->fileNameString) ) { // Copy sid info from file. item->titleString = mySidLoader->getInfoString(0); item->authorString = mySidLoader->getInfoString(1); item->copyrightString = mySidLoader->getInfoString(2); item->songs = mySidLoader->getSongs(); } else { // Sidtune not found. Something is terribly bad! // Insert some defaults. However, this will not // help in case of corrupted format=1 playlists. // But who uses that outdated format anyway? item->titleString = item->fileNameString; item->authorString = item->copyrightString = ""; item->songs = item->subtune; // better than nothing } setModified(); } delete mySidLoader; unlock(); } bool Playlist::haveFileName() { return ( !fileName.isEmpty() ); } bool Playlist::load(const QString& newFileName) { lock(); list.clear(); baseDir = ""; fileName = newFileName; open(fileName); bool haveID = false; loadFormat = -1; bool haveItem = false; PlaylistItem* item; while (status && isGood && !isEOF()) // line-by-line loop { getLine(); // Skip blank and comment lines. while (status && !isEOF() && isBlank() || isComment()) { getLine(); }; if ( isEOF() ) break; #ifdef XSID_WB_DEBUG cout << "Line " << getLineNum() << ", " << getLineLen() << ": "; cout << getLineBuf() << endl; cout << "ParseBuf: " << getParseBuf() << endl; #endif // Evaluate line. if ( !haveID && isValue(keys[group_id],false) ) haveID = true; else { if ( isKey(keys[key_format]) ) loadFormat = atoi(getCurParseBuf()); else if ( isKey(keys[key_baseDir]) ) baseDir = getCurLineBuf(); else if ( isKey(keys[key_file],false) || (loadFormat==1 && isKey(keys[key_fileName],false)) ) { if (haveItem) { list.append(item); } item = new PlaylistItem(); haveItem = true; if ( isKey(keys[key_file]) ) item->fileNameString = getCurLineBuf(); else if ( isKey(keys[key_fileName]) ) item->fileNameString = getCurLineBuf(); #ifdef XSID_WB_DEBUG cout << item->fileNameString << endl; #endif // Incomplete: Convert MS Windows file names. // Remove driveletter prefix, e.g. C: if ( baseDir.isEmpty() && item->fileNameString[1]==':' && item->fileNameString[2]=='\\') { item->fileNameString.remove(0,2); } // Convert separators. for (uint i=0; ifileNameString.length(); i++) { if (item->fileNameString[i] == '\\') item->fileNameString[i] = QDir::separator(); } if ( loadFormat==1 ) { // Format 1 has less file-specific information stored // in the playlist. We need to fix up these values // later when we are sure we have a good path prefix. item->titleString = ""; item->authorString = ""; item->copyrightString = ""; item->songs = 0; item->fadeout = 0; } #ifdef XSID_WB_DEBUG cout << item->fileNameString << endl; #endif } else if ( isKey(keys[key_name]) ) item->titleString = getCurLineBuf(); else if ( isKey(keys[key_author]) ) item->authorString = getCurLineBuf(); else if ( isKey(keys[key_copyright]) ) item->copyrightString = getCurLineBuf(); else if ( isKey(keys[key_songs]) ) item->songs = atoi( getCurParseBuf() ); else if ( isKey(keys[key_subtune]) ) item->subtune = atoi( getCurParseBuf() ); else if ( isKey(keys[key_fadeout]) ) item->fadeout = atoi( getCurParseBuf() ); else if ( isKey(keys[key_time]) ) item->time = atoi( getCurParseBuf() ); else if ( loadFormat==1 && isKey(keys[key_subsong]) ) item->subtune = atoi( getCurParseBuf() ); } }; if (haveItem) // this takes care of the last item { list.append(item); } close(); unlock(); return ( isGood && haveID && loadFormat>=1 && loadFormat<=2 ); } bool Playlist::save(const QString& newFileName) { fileName = newFileName; return save(); } bool Playlist::save() { findBaseDir(); lock(); bool wasSuccess = false; #ifdef XSID_HAVE_IOS_BIN ofstream toFile(fileName,ios::out|ios::bin|ios::trunc); #else ofstream toFile(fileName,ios::out|ios::binary|ios::trunc); #endif if ( !toFile.fail() ) { toFile << ";" << endl << "; Playlist" << endl << "; Saved with SIDPLAY/Linux (X11)." << endl << ";" << endl << endl; toFile << keys[group_id] << endl; writeKey(toFile,keys[key_format]) << saveFormat << endl; if ( !baseDir.isEmpty() ) writeKey(toFile,keys[key_baseDir]) << baseDir << endl; toFile << endl; for (unsigned int n=0; nfileNameString << endl; writeKey(toFile,keys[key_name]) << list.at(n)->titleString << endl; writeKey(toFile,keys[key_author]) << list.at(n)->authorString << endl; writeKey(toFile,keys[key_copyright]) << list.at(n)->copyrightString << endl; writeKey(toFile,keys[key_songs]) << list.at(n)->songs << endl; writeKey(toFile,keys[key_subtune]) << list.at(n)->subtune << endl; if (list.at(n)->time != 0) writeKey(toFile,keys[key_time]) << list.at(n)->time << endl; if (list.at(n)->fadeout != 0) writeKey(toFile,keys[key_fadeout]) << list.at(n)->fadeout << endl; toFile << endl; } toFile.close(); wasSuccess = !toFile.fail(); if ( wasSuccess ) modified = false; } unlock(); removeBaseDir(); return wasSuccess; }