File indexing completed on 2023-05-30 11:30:44
0001 /** 0002 * Copyright (C) 2002-2004 Scott Wheeler <wheeler@kde.org> 0003 * Copyright (C) 2008, 2013 Michael Pyne <mpyne@kde.org> 0004 * 0005 * This program is free software; you can redistribute it and/or modify it under 0006 * the terms of the GNU General Public License as published by the Free Software 0007 * Foundation; either version 2 of the License, or (at your option) any later 0008 * version. 0009 * 0010 * This program is distributed in the hope that it will be useful, but WITHOUT ANY 0011 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 0012 * PARTICULAR PURPOSE. See the GNU General Public License for more details. 0013 * 0014 * You should have received a copy of the GNU General Public License along with 0015 * this program. If not, see <http://www.gnu.org/licenses/>. 0016 */ 0017 0018 #include "cache.h" 0019 #include "juk-exception.h" 0020 0021 #include <kmessagebox.h> 0022 #include <kconfig.h> 0023 #include <ktoggleaction.h> 0024 #include <KLocalizedString> 0025 0026 #include <QDir> 0027 #include <QSaveFile> 0028 0029 #include "juktag.h" 0030 #include "searchplaylist.h" 0031 #include "historyplaylist.h" 0032 #include "upcomingplaylist.h" 0033 #include "folderplaylist.h" 0034 #include "playlistcollection.h" 0035 #include "actioncollection.h" 0036 #include "juk.h" 0037 #include "juk_debug.h" 0038 0039 using namespace ActionCollection; 0040 0041 const int Cache::playlistListCacheVersion = 3; 0042 const int Cache::playlistItemsCacheVersion = 2; 0043 0044 enum PlaylistType 0045 { 0046 Normal = 0, 0047 Search = 1, 0048 History = 2, 0049 Upcoming = 3, 0050 Folder = 4 0051 }; 0052 0053 //////////////////////////////////////////////////////////////////////////////// 0054 // public methods 0055 //////////////////////////////////////////////////////////////////////////////// 0056 0057 Cache *Cache::instance() 0058 { 0059 static Cache cache; 0060 return &cache; 0061 } 0062 0063 static void parsePlaylistStream(QDataStream &s, PlaylistCollection *collection) 0064 { 0065 while(!s.atEnd()) { 0066 qint32 playlistType; 0067 s >> playlistType; 0068 0069 Playlist *playlist = nullptr; 0070 0071 switch(playlistType) { 0072 case Search: 0073 { 0074 SearchPlaylist *p = new SearchPlaylist(collection, *(new PlaylistSearch(JuK::JuKInstance()))); 0075 s >> *p; 0076 playlist = p; 0077 break; 0078 } 0079 case History: 0080 { 0081 action<KToggleAction>("showHistory")->setChecked(true); 0082 collection->setHistoryPlaylistEnabled(true); 0083 s >> *collection->historyPlaylist(); 0084 playlist = collection->historyPlaylist(); 0085 break; 0086 } 0087 case Upcoming: 0088 { 0089 collection->setUpcomingPlaylistEnabled(true); 0090 UpcomingPlaylist *p = collection->upcomingPlaylist(); 0091 action<KToggleAction>("saveUpcomingTracks")->setChecked(true); 0092 s >> *p; 0093 playlist = p; 0094 break; 0095 } 0096 case Folder: 0097 { 0098 FolderPlaylist *p = new FolderPlaylist(collection); 0099 s >> *p; 0100 playlist = p; 0101 break; 0102 } 0103 case Normal: 0104 { 0105 Playlist *p = new Playlist(collection, true); 0106 s >> *p; 0107 0108 // We may have already read this playlist from the folder 0109 // scanner, if an .m3u playlist 0110 if(collection->containsPlaylistFile(p->fileName())) { 0111 delete p; 0112 p = nullptr; 0113 } 0114 0115 playlist = p; 0116 break; 0117 } 0118 default: 0119 qCWarning(JUK_LOG) << "Unknown playlist type" << playlistType << "loading saved playlists"; 0120 throw BICStreamException(); 0121 } // switch 0122 0123 qint32 sortColumn; 0124 s >> sortColumn; 0125 if(playlist) 0126 playlist->sortByColumn(sortColumn); 0127 } 0128 } 0129 0130 void Cache::loadPlaylists(PlaylistCollection *collection) // static 0131 { 0132 const QString playlistsFile = playlistsCacheFileName(); 0133 QFile f(playlistsFile); 0134 0135 if(!f.open(QIODevice::ReadOnly)) 0136 return; 0137 0138 QDataStream fs(&f); 0139 0140 qint32 version; 0141 fs >> version; 0142 0143 if(version != 3 || fs.status() != QDataStream::Ok) { 0144 // Either the file is corrupt or is from a truly ancient version 0145 // of JuK. 0146 qCWarning(JUK_LOG) << "Found the playlist cache but it was clearly corrupt."; 0147 return; 0148 } 0149 0150 // Our checksum is only for the values after the version and checksum so 0151 // we want to get a byte array with just the checksummed data. 0152 0153 QByteArray data; 0154 quint16 checksum; 0155 fs >> checksum >> data; 0156 0157 if(fs.status() != QDataStream::Ok || checksum != qChecksum(data.data(), data.size())) 0158 return; 0159 0160 QDataStream s(&data, QIODevice::ReadOnly); 0161 s.setVersion(QDataStream::Qt_4_3); 0162 0163 try { // Loading failures are indicated by an exception 0164 parsePlaylistStream(s, collection); 0165 } 0166 catch(BICStreamException &) { 0167 qCCritical(JUK_LOG) << "Exception loading playlists - binary incompatible stream."; 0168 // TODO Restructure the Playlist data model and PlaylistCollection data model 0169 // to be separate from the view/controllers. 0170 return; 0171 } 0172 } 0173 0174 void Cache::savePlaylists(const PlaylistList &playlists) 0175 { 0176 QString playlistsFile = playlistsCacheFileName(); 0177 QSaveFile f(playlistsFile); 0178 0179 if(!f.open(QIODevice::WriteOnly)) { 0180 qCCritical(JUK_LOG) << "Error saving collection:" << f.errorString(); 0181 return; 0182 } 0183 0184 QByteArray data; 0185 QDataStream s(&data, QIODevice::WriteOnly); 0186 s.setVersion(QDataStream::Qt_4_3); 0187 0188 for(const auto &it : playlists) { 0189 if(!(it)) { 0190 continue; 0191 } 0192 // TODO back serialization type into Playlist itself 0193 if(dynamic_cast<HistoryPlaylist *>(it)) { 0194 s << qint32(History) 0195 << *static_cast<HistoryPlaylist *>(it); 0196 } 0197 else if(dynamic_cast<SearchPlaylist *>(it)) { 0198 s << qint32(Search) 0199 << *static_cast<SearchPlaylist *>(it); 0200 } 0201 else if(dynamic_cast<UpcomingPlaylist *>(it)) { 0202 if(!action<KToggleAction>("saveUpcomingTracks")->isChecked()) 0203 continue; 0204 s << qint32(Upcoming) 0205 << *static_cast<UpcomingPlaylist *>(it); 0206 } 0207 else if(dynamic_cast<FolderPlaylist *>(it)) { 0208 s << qint32(Folder) 0209 << *static_cast<FolderPlaylist *>(it); 0210 } 0211 else { 0212 s << qint32(Normal) 0213 << *(it); 0214 } 0215 s << qint32(it->sortColumn()); 0216 } 0217 0218 QDataStream fs(&f); 0219 fs << qint32(playlistListCacheVersion); 0220 fs << qChecksum(data.data(), data.size()); 0221 0222 fs << data; 0223 0224 if(!f.commit()) 0225 qCCritical(JUK_LOG) << "Error saving collection:" << f.errorString(); 0226 } 0227 0228 void Cache::ensureAppDataStorageExists() // static 0229 { 0230 QString dirPath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation); 0231 QDir appDataDir(dirPath); 0232 0233 if(!appDataDir.exists() && !appDataDir.mkpath(dirPath)) 0234 qCCritical(JUK_LOG) << "Unable to create appdata storage in" << dirPath; 0235 } 0236 0237 bool Cache::cacheFileExists() // static 0238 { 0239 return QFile::exists(fileHandleCacheFileName()); 0240 } 0241 0242 // Despite the 'Cache' class name, these data files are not regenerable and so 0243 // should not be stored in cache directory. 0244 QString Cache::fileHandleCacheFileName() // static 0245 { 0246 return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/cache"; 0247 } 0248 0249 QString Cache::playlistsCacheFileName() // static 0250 { 0251 return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/playlists"; 0252 } 0253 0254 //////////////////////////////////////////////////////////////////////////////// 0255 // private methods 0256 //////////////////////////////////////////////////////////////////////////////// 0257 0258 Cache::Cache() 0259 { 0260 0261 } 0262 0263 bool Cache::prepareToLoadCachedItems() 0264 { 0265 m_loadFile.setFileName(fileHandleCacheFileName()); 0266 if(!m_loadFile.open(QIODevice::ReadOnly)) 0267 return false; 0268 0269 m_loadDataStream.setDevice(&m_loadFile); 0270 0271 int dataStreamVersion = CacheDataStream::Qt_3_3; 0272 0273 qint32 version; 0274 m_loadDataStream >> version; 0275 0276 switch(version) { 0277 case 2: 0278 dataStreamVersion = CacheDataStream::Qt_4_3; 0279 Q_FALLTHROUGH(); 0280 0281 // Other than that we're compatible with cache v1, so fallthrough 0282 // to setCacheVersion 0283 0284 case 1: { 0285 m_loadDataStream.setCacheVersion(1); 0286 m_loadDataStream.setVersion(dataStreamVersion); 0287 0288 qint32 checksum; 0289 m_loadDataStream >> checksum 0290 >> m_loadFileBuffer.buffer(); 0291 0292 m_loadFileBuffer.open(QIODevice::ReadOnly); 0293 m_loadDataStream.setDevice(&m_loadFileBuffer); 0294 0295 qint32 checksumExpected = qChecksum( 0296 m_loadFileBuffer.data(), m_loadFileBuffer.size()); 0297 if(m_loadDataStream.status() != CacheDataStream::Ok || 0298 checksum != checksumExpected) 0299 { 0300 qCCritical(JUK_LOG) << "Music cache checksum expected to get" << checksumExpected << 0301 "actually was" << checksum; 0302 KMessageBox::error(0, i18n("The music data cache has been corrupted. JuK " 0303 "needs to rescan it now. This may take some time.")); 0304 return false; 0305 } 0306 0307 break; 0308 } 0309 default: { 0310 m_loadDataStream.device()->reset(); 0311 m_loadDataStream.setCacheVersion(0); 0312 0313 // This cache is so old that this is just a wild guess here that 3.3 0314 // is compatible. 0315 m_loadDataStream.setVersion(CacheDataStream::Qt_3_3); 0316 break; 0317 } 0318 } 0319 0320 return true; 0321 } 0322 0323 FileHandle Cache::loadNextCachedItem() 0324 { 0325 if(!m_loadFile.isOpen() || !m_loadDataStream.device()) { 0326 qCWarning(JUK_LOG) << "Already completed reading cache file."; 0327 return FileHandle(); 0328 } 0329 0330 if(m_loadDataStream.status() == QDataStream::ReadCorruptData) { 0331 qCCritical(JUK_LOG) << "Attempted to read file handle from corrupt cache file."; 0332 return FileHandle(); 0333 } 0334 0335 if(!m_loadDataStream.atEnd()) { 0336 QString fileName; 0337 m_loadDataStream >> fileName; 0338 fileName.squeeze(); 0339 0340 return FileHandle(fileName, m_loadDataStream); 0341 } 0342 else { 0343 m_loadDataStream.setDevice(0); 0344 m_loadFile.close(); 0345 0346 return FileHandle(); 0347 } 0348 } 0349 0350 // vim: set et sw=4 tw=0 sta: