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