diff --git a/src/App.cpp b/src/App.cpp index a9c9959799..8aa87ee477 100644 --- a/src/App.cpp +++ b/src/App.cpp @@ -1,643 +1,643 @@ /**************************************************************************************** * Copyright (c) 2002 Mark Kretschmann * * * * 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, see . * ****************************************************************************************/ #include "App.h" #include #include "EngineController.h" #include "KNotificationBackend.h" #include "PluginManager.h" #include "scripting/scriptmanager/ScriptManager.h" #include "TrayIcon.h" #include "amarokconfig.h" #include "amarokurls/AmarokUrl.h" #include "configdialog/ConfigDialog.h" #include "configdialog/dialogs/PlaybackConfig.h" #include "core/capabilities/SourceInfoCapability.h" #include "core/interfaces/Logger.h" #include "core/meta/Meta.h" #include "core/meta/support/MetaConstants.h" #include "core/meta/support/MetaUtility.h" #include "core/playlists/Playlist.h" #include "core/playlists/PlaylistFormat.h" #include "core/podcasts/PodcastProvider.h" #include "core/support/Amarok.h" #include "core/support/Components.h" #include "core/support/Debug.h" #include "core/transcoding/TranscodingController.h" #include "core-impl/collections/support/CollectionManager.h" #include "core-impl/playlists/types/file/PlaylistFileSupport.h" #include "core-impl/storage/StorageManager.h" #include "covermanager/CoverCache.h" #include "covermanager/CoverFetcher.h" #include "dbus/CollectionDBusHandler.h" #include "dbus/mpris1/PlayerHandler.h" #include "dbus/mpris1/RootHandler.h" #include "dbus/mpris1/TrackListHandler.h" #include "dbus/mpris2/Mpris2.h" #include "network/NetworkAccessManagerProxy.h" #include "playlist/PlaylistActions.h" #include "playlist/PlaylistController.h" #include "playlist/PlaylistModelStack.h" #include "playlistmanager/PlaylistManager.h" #include "services/ServicePluginManager.h" #include "scripting/scriptconsole/ScriptConsole.h" #include "statemanagement/ApplicationController.h" #include "statemanagement/DefaultApplicationController.h" #include "statsyncing/Controller.h" #include "widgets/Osd.h" #include #include #include #include //initCliArgs() #include #include //slotConfigToolbars() #include #include #include #include #include #include #include //slotConfigShortcuts() #include #include #include #include #include #include // for Qt::escape() #include //showHyperThreadingWarning() #include #ifdef Q_WS_MAC #include extern void setupEventHandler_mac(SRefCon); #endif QStringList App::s_delayedAmarokUrls = QStringList(); AMAROK_EXPORT OcsData ocsData( "opendesktop" ); App::App() : KUniqueApplication() , m_tray(0) { DEBUG_BLOCK PERF_LOG( "Begin Application ctor" ) // required for last.fm plugin to grab app version setApplicationVersion( AMAROK_VERSION ); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); #ifdef Q_WS_MAC // this is inspired by OpenSceneGraph: osgDB/FilePath.cpp // Start with the Bundle PlugIns directory. // Get the main bundle first. No need to retain or release it since // we are not keeping a reference CFBundleRef myBundle = CFBundleGetMainBundle(); if( myBundle ) { // CFBundleGetMainBundle will return a bundle ref even if // the application isn't part of a bundle, so we need to // check // if the path to the bundle ends in ".app" to see if it is // a // proper application bundle. If it is, the plugins path is // added CFURLRef urlRef = CFBundleCopyBundleURL(myBundle); if(urlRef) { char bundlePath[1024]; if( CFURLGetFileSystemRepresentation( urlRef, true, (UInt8 *)bundlePath, sizeof(bundlePath) ) ) { QByteArray bp( bundlePath ); size_t len = bp.length(); if( len > 4 && bp.right( 4 ) == ".app" ) { bp.append( "/Contents/MacOS" ); QByteArray path = qgetenv( "PATH" ); if( path.length() > 0 ) { path.prepend( ":" ); } path.prepend( bp ); debug() << "setting PATH=" << path; setenv("PATH", path, 1); } } // docs say we are responsible for releasing CFURLRef CFRelease(urlRef); } } setupEventHandler_mac(this); #endif PERF_LOG( "Done App ctor" ) continueInit(); } App::~App() { DEBUG_BLOCK CollectionManager::instance()->stopScan(); // Hiding the OSD before exit prevents crash Amarok::OSD::instance()->hide(); // This following can't go in the PlaylistModel destructor, because by the time that // happens, the Config has already been written. // Use the bottom model because that provides the most dependable/invariable row // number to save in an external file. AmarokConfig::setLastPlaying( Playlist::ModelStack::instance()->bottom()->activeRow() ); if ( AmarokConfig::resumePlayback() ) { Meta::TrackPtr engineTrack = The::engineController()->currentTrack(); if( engineTrack ) { AmarokConfig::setResumeTrack( engineTrack->playableUrl().prettyUrl() ); AmarokConfig::setResumeTime( The::engineController()->trackPositionMs() ); AmarokConfig::setResumePaused( The::engineController()->isPaused() ); } else AmarokConfig::setResumeTrack( QString() ); //otherwise it'll play previous resume next time! } The::engineController()->endSession(); //records final statistics #ifndef Q_WS_MAC // do even if trayicon is not shown, it is safe Amarok::config().writeEntry( "HiddenOnExit", mainWindow()->isHidden() ); AmarokConfig::self()->writeConfig(); #else // for some reason on OS X the main window always reports being hidden // this means if you have the tray icon enabled, amarok will always open minimized Amarok::config().writeEntry( "HiddenOnExit", false ); AmarokConfig::self()->writeConfig(); #endif ScriptManager::destroy(); // this must be deleted before the connection to the Xserver is // severed, or we risk a crash when the QApplication is exited, // I asked Trolltech! *smug* Amarok::OSD::destroy(); Amarok::KNotificationBackend::destroy(); AmarokConfig::self()->writeConfig(); //mainWindow()->deleteBrowsers(); delete mainWindow(); Playlist::Controller::destroy(); Playlist::ModelStack::destroy(); Playlist::Actions::destroy(); PlaylistManager::destroy(); CoverFetcher::destroy(); CoverCache::destroy(); ServicePluginManager::destroy(); CollectionManager::destroy(); StorageManager::destroy(); NetworkAccessManagerProxy::destroy(); Plugins::PluginManager::destroy(); //this should be moved to App::quit() I guess Amarok::Components::applicationController()->shutdown(); #ifdef Q_WS_WIN // work around for KUniqueApplication being not completely implemented on windows QDBusConnectionInterface* dbusService; if (QDBusConnection::sessionBus().isConnected() && (dbusService = QDBusConnection::sessionBus().interface())) { dbusService->unregisterService("org.mpris.amarok"); dbusService->unregisterService("org.mpris.MediaPlayer2.amarok"); } #endif } void App::handleCliArgs() //static { DEBUG_BLOCK KCmdLineArgs* const args = KCmdLineArgs::parsedArgs(); if( args->isSet( "cwd" ) ) KCmdLineArgs::setCwd( args->getOption( "cwd" ).toLocal8Bit() ); bool haveArgs = true; // assume having args in first place if( args->count() > 0 ) { - KUrl::List list; + QList list; for( int i = 0; i < args->count(); i++ ) { - KUrl url = args->url( i ); + QUrl url = args->url( i ); //TODO:PORTME if( Podcasts::PodcastProvider::couldBeFeed( url.url() ) ) { - KUrl feedUrl = Podcasts::PodcastProvider::toFeedUrl( url.url() ); + QUrl feedUrl = Podcasts::PodcastProvider::toFeedUrl( url.url() ); The::playlistManager()->defaultPodcasts()->addPodcast( feedUrl ); } - else if( url.protocol() == "amarok" ) + else if( url.scheme() == "amarok" ) s_delayedAmarokUrls.append( url.url() ); else list << url; } Playlist::AddOptions options; if( args->isSet( "queue" ) ) options = Playlist::OnQueueToPlaylistAction; else if( args->isSet( "append" ) ) options = Playlist::OnAppendToPlaylistAction; else if( args->isSet( "load" ) ) options = Playlist::OnReplacePlaylistAction; else options = Playlist::OnPlayMediaAction; The::playlistController()->insertOptioned( list, options ); } else if ( args->isSet( "cdplay" ) ) The::mainWindow()->playAudioCd(); //we shouldn't let the user specify two of these since it is pointless! //so we prioritise, pause > stop > play > next > prev //thus pause is the least destructive, followed by stop as brakes are the most important bit of a car(!) //then the others seemed sensible. Feel free to modify this order, but please leave justification in the cvs log //I considered doing some sanity checks (eg only stop if paused or playing), but decided it wasn't worth it else if ( args->isSet( "pause" ) ) The::engineController()->pause(); else if ( args->isSet( "stop" ) ) The::engineController()->stop(); else if ( args->isSet( "play-pause" ) ) The::engineController()->playPause(); else if ( args->isSet( "play" ) ) //will restart if we are playing The::engineController()->play(); else if ( args->isSet( "next" ) ) The::playlistActions()->next(); else if ( args->isSet( "previous" ) ) The::playlistActions()->back(); else // no args given haveArgs = false; static bool firstTime = true; //allows debugging on OS X. Bundles have to be started with "open". Therefore it is not possible to pass an argument const bool forceDebug = Amarok::config().readEntry( "Force Debug", false ); if( firstTime && !Debug::debugEnabled() && !forceDebug ) { qDebug() << "**********************************************************************************************"; qDebug() << "** AMAROK WAS STARTED IN NORMAL MODE. IF YOU WANT TO SEE DEBUGGING INFORMATION, PLEASE USE: **"; qDebug() << "** amarok --debug **"; qDebug() << "**********************************************************************************************"; } if( !firstTime && !haveArgs ) { // mainWindow() can be 0 if another instance is loading, see https://bugs.kde.org/show_bug.cgi?id=202713 if( pApp->mainWindow() ) pApp->mainWindow()->activate(); } firstTime = false; args->clear(); //free up memory } ///////////////////////////////////////////////////////////////////////////////////// // INIT ///////////////////////////////////////////////////////////////////////////////////// void App::initCliArgs() //static { // Update main.cpp (below KUniqueApplication::start() wrt instanceOptions) aswell if needed! KCmdLineOptions options; options.add("+[URL(s)]", ki18n( "Files/URLs to open" )); options.add("cdplay", ki18n("Immediately start playing an audio cd")); options.add("r"); options.add("previous", ki18n( "Skip backwards in playlist" )); options.add("p"); options.add("play", ki18n( "Start playing current playlist" )); options.add("t"); options.add("play-pause", ki18n( "Play if stopped, pause if playing" )); options.add("pause", ki18n( "Pause playback" )); options.add("s"); options.add("stop", ki18n( "Stop playback" )); options.add("f"); options.add("next", ki18n( "Skip forwards in playlist" )); options.add(":", ki18n("Additional options:")); options.add("a"); options.add("append", ki18n( "Append files/URLs to playlist" )); options.add("queue", ki18n("Queue URLs after the currently playing track")); options.add("l"); options.add("load", ki18n("Load URLs, replacing current playlist")); options.add("d"); options.add("debug", ki18n("Print verbose debugging information")); options.add("debug-audio", ki18n("Print verbose debugging information from the audio system")); options.add("c"); options.add("coloroff", ki18n("Disable colorization for debug output.")); options.add("m"); options.add("multipleinstances", ki18n("Allow running multiple Amarok instances")); options.add("cwd ", ki18n( "Base for relative filenames/URLs" )); KCmdLineArgs::addCmdLineOptions( options ); //add our own options } ///////////////////////////////////////////////////////////////////////////////////// // METHODS ///////////////////////////////////////////////////////////////////////////////////// //SLOT void App::applySettings( bool firstTime ) { ///Called when the configDialog is closed with OK or Apply DEBUG_BLOCK if( AmarokConfig::showTrayIcon() && ! m_tray ) { m_tray = new Amarok::TrayIcon( m_mainWindow.data() ); } else if( !AmarokConfig::showTrayIcon() && m_tray ) { delete m_tray; m_tray = 0; } if( !firstTime ) // prevent OSD from popping up during startup Amarok::OSD::instance()->applySettings(); if( !firstTime ) emit settingsChanged(); if( AmarokConfig::enableScriptConsole() && !m_scriptConsole ) m_scriptConsole = ScriptConsoleNS::ScriptConsole::instance(); else if( !AmarokConfig::enableScriptConsole() && m_scriptConsole ) m_scriptConsole.data()->deleteLater(); } //SLOT void App::continueInit() { DEBUG_BLOCK PERF_LOG( "Begin App::continueInit" ) const KCmdLineArgs* const args = KCmdLineArgs::parsedArgs(); const bool restoreSession = args->count() == 0 || args->isSet( "append" ) || args->isSet( "queue" ) || Amarok::config().readEntry( "AppendAsDefault", false ); QTextCodec* utf8codec = QTextCodec::codecForName( "UTF-8" ); QTextCodec::setCodecForCStrings( utf8codec ); //We need this to make CollectionViewItem showing the right characters. new Amarok::DefaultApplicationController( this ); Amarok::Components::applicationController()->start(); // Instantiate statistics synchronization controller. Needs to be before creating // MainWindow as MainWindow connects a signal to StatSyncing::Controller. Amarok::Components::setStatSyncingController( new StatSyncing::Controller( this ) ); PERF_LOG( "Creating MainWindow" ) m_mainWindow = new MainWindow(); PERF_LOG( "Done creating MainWindow" ) if( AmarokConfig::showTrayIcon() ) m_tray = new Amarok::TrayIcon( mainWindow() ); PERF_LOG( "Creating DBus handlers" ) new Mpris1::RootHandler(); new Mpris1::PlayerHandler(); new Mpris1::TrackListHandler(); QDBusConnection::sessionBus().registerService("org.mpris.amarok"); new CollectionDBusHandler( this ); new Amarok::Mpris2( this ); PERF_LOG( "Done creating DBus handlers" ) //DON'T DELETE THIS NEXT LINE or the app crashes when you click the X (unless we reimplement closeEvent) //Reason: in ~App we have to call the deleteBrowsers method or else we run afoul of refcount foobar in KHTMLPart //But if you click the X (not Action->Quit) it automatically kills MainWindow because KMainWindow sets this //for us as default (bad KMainWindow) mainWindow()->setAttribute( Qt::WA_DeleteOnClose, false ); //init playlist window as soon as the database is guaranteed to be usable // Create engine, show TrayIcon etc. applySettings( true ); // Must be created _after_ MainWindow. PERF_LOG( "Starting ScriptManager" ) ScriptManager::instance(); PERF_LOG( "ScriptManager started" ) The::engineController()->setVolume( AmarokConfig::masterVolume() ); The::engineController()->setMuted( AmarokConfig::muteState() ); Amarok::KNotificationBackend::instance()->setEnabled( AmarokConfig::kNotifyEnabled() ); Amarok::OSD::instance()->applySettings(); // Create after setting volume (don't show OSD for that) // Restore keyboard shortcuts etc from config Amarok::actionCollection()->readSettings(); //on startup we need to show the window, but only if it wasn't hidden on exit //and always if the trayicon isn't showing if( !Amarok::config().readEntry( "HiddenOnExit", false ) || !AmarokConfig::showTrayIcon() ) { PERF_LOG( "showing main window again" ) m_mainWindow.data()->show(); PERF_LOG( "after showing mainWindow" ) } //Instantiate the Transcoding::Controller, this fires up an asynchronous KProcess with //FFmpeg which should not take more than ~200msec. Amarok::Components::setTranscodingController( new Transcoding::Controller( this ) ); PERF_LOG( "App init done" ) // check that the amarok sql configuration is valid. if( !StorageManager::instance()->getLastErrors().isEmpty() ) { KMessageBox::error( The::mainWindow(), i18n( "The amarok database reported " "the following errors:\n%1\nIn most cases you will need to resolve " "these errors before Amarok will run properly." ). arg( StorageManager::instance()->getLastErrors().join( "\n" ) ), i18n( "Database Error" )); StorageManager::instance()->clearLastErrors(); slotConfigAmarok( "DatabaseConfig" ); } else { handleFirstRun(); } if( AmarokConfig::resumePlayback() && restoreSession && !args->isSet( "stop" ) ) { //restore session as long as the user didn't specify media to play etc. //do this after applySettings() so OSD displays correctly The::engineController()->restoreSession(); } //and now we can run any amarokurls provided on startup, as all components should be initialized by now! foreach( const QString& urlString, s_delayedAmarokUrls ) { AmarokUrl aUrl( urlString ); aUrl.run(); } s_delayedAmarokUrls.clear(); } void App::slotConfigAmarok( const QString& page ) { KConfigDialog *dialog = KConfigDialog::exists( "settings" ); if( !dialog ) { //KConfigDialog didn't find an instance of this dialog, so lets create it : dialog = new Amarok2ConfigDialog( mainWindow(), "settings", AmarokConfig::self() ); connect( dialog, SIGNAL(settingsChanged(QString)), SLOT(applySettings()) ); } static_cast( dialog )->show( page ); } void App::slotConfigShortcuts() { KShortcutsDialog::configure( Amarok::actionCollection(), KShortcutsEditor::LetterShortcutsAllowed, mainWindow() ); AmarokConfig::self()->writeConfig(); } -KIO::Job *App::trashFiles( const KUrl::List &files ) +KIO::Job *App::trashFiles( const QList &files ) { KIO::Job *job = KIO::trash( files ); Amarok::Components::logger()->newProgressOperation( job, i18n("Moving files to trash") ); connect( job, SIGNAL(result(KJob*)), this, SLOT(slotTrashResult(KJob*)) ); return job; } void App::slotTrashResult( KJob *job ) { if( job->error() ) job->uiDelegate()->showErrorMessage(); } void App::quit() { DEBUG_BLOCK The::playlistManager()->completePodcastDownloads(); // Following signal is relayed to scripts, which may block quitting for a while emit prepareToQuit(); KApplication::quit(); } bool App::event( QEvent *event ) { switch( event->type() ) { //allows Amarok to open files from the finder on OS X case QEvent::FileOpen: { QString file = static_cast( event )->file(); - The::playlistController()->insertOptioned( KUrl( file ), Playlist::OnPlayMediaAction ); + The::playlistController()->insertOptioned( QUrl( file ), Playlist::OnPlayMediaAction ); return true; } default: return KUniqueApplication::event( event ); } } int App::newInstance() { DEBUG_BLOCK static bool first = true; if ( isSessionRestored() && first ) { first = false; return 0; } first = false; handleCliArgs(); return 0; } void App::handleFirstRun() { KConfigGroup config = KGlobal::config()->group( "General" ); if( !config.readEntry( "First Run", true ) ) return; - const KUrl musicUrl = QDesktopServices::storageLocation( QDesktopServices::MusicLocation ); - const QString musicDir = musicUrl.toLocalFile( KUrl::RemoveTrailingSlash ); + const QUrl musicUrl = QDesktopServices::storageLocation( QDesktopServices::MusicLocation ); + const QString musicDir = musicUrl.toLocalFile( QUrl::RemoveTrailingSlash ); const QDir dir( musicDir ); int result = KMessageBox::No; if( dir.exists() && dir.isReadable() ) { result = KMessageBox::questionYesNoCancel( mainWindow(), i18n( "A music path, " "%1, is set in System Settings.\nWould you like to use that as a " "collection folder?", musicDir ) ); } switch( result ) { case KMessageBox::Yes: { Collections::Collection *coll = CollectionManager::instance()->primaryCollection(); if( coll ) { coll->setProperty( "collectionFolders", QStringList() << musicDir ); CollectionManager::instance()->startFullScan(); } break; } case KMessageBox::No: slotConfigAmarok( "CollectionConfig" ); break; default: break; } config.writeEntry( "First Run", false ); } diff --git a/src/App.h b/src/App.h index bdb38e5dc8..f146c62741 100644 --- a/src/App.h +++ b/src/App.h @@ -1,108 +1,108 @@ /**************************************************************************************** * Copyright (c) 2002 Mark Kretschmann * * * * 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, see . * ****************************************************************************************/ #ifndef AMAROK_APP_H #define AMAROK_APP_H #include "MainWindow.h" #include "amarok_export.h" #include #include "aboutdialog/OcsData.h" #include #include //baseclass -#include +#include #include #include #include namespace Amarok { class TrayIcon; } namespace ScriptConsoleNS{ class ScriptConsole; } class OcsData; namespace KIO { class Job; } class KJob; class MediaDeviceManager; class AMAROK_EXPORT App : public KUniqueApplication { Q_OBJECT public: App(); ~App(); static App *instance() { return static_cast( kapp ); } void setUniqueInstance( bool isUnique ) { m_isUniqueInstance = isUnique; } bool isNonUniqueInstance() const { return m_isUniqueInstance; } Amarok::TrayIcon* trayIcon() const { return m_tray; } static void handleCliArgs(); static void initCliArgs(); virtual int newInstance(); inline MainWindow *mainWindow() const { return m_mainWindow.data(); } // FRIENDS friend class MainWindow; //requires access to applySettings() signals: void prepareToQuit(); void settingsChanged(); private slots: void continueInit(); public slots: void applySettings( bool firstTime = false ); void slotConfigAmarok( const QString& page = QString() ); void slotConfigShortcuts(); - KIO::Job *trashFiles( const KUrl::List &files ); + KIO::Job *trashFiles( const QList &files ); void quit(); protected: virtual bool event( QEvent *event ); private slots: void slotTrashResult( KJob *job ); private: void handleFirstRun(); // ATTRIBUTES bool m_isUniqueInstance; QWeakPointer m_mainWindow; Amarok::TrayIcon *m_tray; MediaDeviceManager *m_mediaDeviceManager; QWeakPointer m_scriptConsole; static QStringList s_delayedAmarokUrls; }; #define pApp static_cast(kapp) #endif // AMAROK_APP_H diff --git a/src/EngineController.cpp b/src/EngineController.cpp index 76ea95b42c..8457620135 100644 --- a/src/EngineController.cpp +++ b/src/EngineController.cpp @@ -1,1379 +1,1379 @@ /**************************************************************************************** * Copyright (c) 2004 Frederik Holljen * * Copyright (c) 2004,2005 Max Howell * * Copyright (c) 2004-2013 Mark Kretschmann * * Copyright (c) 2006,2008 Ian Monroe * * Copyright (c) 2008 Jason A. Donenfeld * * Copyright (c) 2009 Nikolaj Hald Nielsen * * Copyright (c) 2009 Artur Szymiec * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "EngineController" #include "EngineController.h" #include "MainWindow.h" #include "amarokconfig.h" #include "core-impl/collections/support/CollectionManager.h" #include "core/capabilities/MultiPlayableCapability.h" #include "core/capabilities/MultiSourceCapability.h" #include "core/capabilities/SourceInfoCapability.h" #include "core/interfaces/Logger.h" #include "core/meta/Meta.h" #include "core/meta/support/MetaConstants.h" #include "core/meta/support/MetaUtility.h" #include "core/support/Amarok.h" #include "core/support/Components.h" #include "core/support/Debug.h" #include "playback/DelayedDoers.h" #include "playback/Fadeouter.h" #include "playback/PowerManager.h" #include "playlist/PlaylistActions.h" #include #include #include #include #include #include #include // for Qt::escape #include // for slotMetaDataChanged() typedef QPair FieldPair; namespace The { EngineController* engineController() { return EngineController::instance(); } } EngineController * EngineController::instance() { return Amarok::Components::engineController(); } EngineController::EngineController() : m_boundedPlayback( 0 ) , m_multiPlayback( 0 ) , m_multiSource( 0 ) , m_playWhenFetched( true ) , m_volume( 0 ) , m_currentAudioCdTrack( 0 ) , m_pauseTimer( new QTimer( this ) ) , m_lastStreamStampPosition( -1 ) , m_ignoreVolumeChangeAction ( false ) , m_ignoreVolumeChangeObserve ( false ) , m_tickInterval( 0 ) , m_lastTickPosition( -1 ) , m_lastTickCount( 0 ) , m_mutex( QMutex::Recursive ) { DEBUG_BLOCK // ensure this object is created in a main thread Q_ASSERT( thread() == QCoreApplication::instance()->thread() ); connect( this, SIGNAL(fillInSupportedMimeTypes()), SLOT(slotFillInSupportedMimeTypes()) ); connect( this, SIGNAL(trackFinishedPlaying(Meta::TrackPtr,double)), SLOT(slotTrackFinishedPlaying(Meta::TrackPtr,double)) ); new PowerManager( this ); // deals with inhibiting suspend etc. m_pauseTimer->setSingleShot( true ); connect( m_pauseTimer, SIGNAL(timeout()), SLOT(slotPause() ) ); m_equalizerController = new EqualizerController( this ); } EngineController::~EngineController() { DEBUG_BLOCK //we like to know when singletons are destroyed // don't do any of the after-processing that normally happens when // the media is stopped - that's what endSession() is for if( m_media ) { m_media.data()->blockSignals(true); m_media.data()->stop(); } delete m_boundedPlayback; m_boundedPlayback = 0; delete m_multiPlayback; // need to get a new instance of multi if played again m_multiPlayback = 0; delete m_media.data(); delete m_audio.data(); delete m_audioDataOutput.data(); } void EngineController::initializePhonon() { DEBUG_BLOCK m_path.disconnect(); m_dataPath.disconnect(); // QWeakPointers reset themselves to null if the object is deleted delete m_media.data(); delete m_controller.data(); delete m_audio.data(); delete m_audioDataOutput.data(); delete m_preamp.data(); delete m_fader.data(); using namespace Phonon; PERF_LOG( "EngineController: loading phonon objects" ) m_media = new MediaObject( this ); // Enable zeitgeist support on linux //TODO: make this configurable by the user. m_media.data()->setProperty( "PlaybackTracking", true ); m_audio = new AudioOutput( MusicCategory, this ); m_audioDataOutput = new AudioDataOutput( this ); m_audioDataOutput.data()->setDataSize( DATAOUTPUT_DATA_SIZE ); // The number of samples that Phonon sends per signal m_path = createPath( m_media.data(), m_audio.data() ); m_controller = new MediaController( m_media.data() ); m_equalizerController->initialize( m_path ); // HACK we turn off replaygain manually on OSX, until the phonon coreaudio backend is fixed. // as the default is specified in the .cfg file, we can't just tell it to be a different default on OSX #ifdef Q_WS_MAC AmarokConfig::setReplayGainMode( AmarokConfig::EnumReplayGainMode::Off ); AmarokConfig::setFadeoutOnStop( false ); #endif // we now try to create pre-amp unconditionally, however we check that it is valid. // So now m_preamp is null equals not available at all QScopedPointer preamp( new VolumeFaderEffect( this ) ); if( preamp->isValid() ) { m_preamp = preamp.take(); m_path.insertEffect( m_preamp.data() ); } QScopedPointer fader( new VolumeFaderEffect( this ) ); if( fader->isValid() ) { fader->setFadeCurve( VolumeFaderEffect::Fade9Decibel ); m_fader = fader.take(); m_path.insertEffect( m_fader.data() ); m_dataPath = createPath( m_fader.data(), m_audioDataOutput.data() ); } else m_dataPath = createPath( m_media.data(), m_audioDataOutput.data() ); m_media.data()->setTickInterval( 100 ); m_tickInterval = m_media.data()->tickInterval(); debug() << "Tick Interval (actual): " << m_tickInterval; PERF_LOG( "EngineController: loaded phonon objects" ) // Get the next track when there is 2 seconds left on the current one. m_media.data()->setPrefinishMark( 2000 ); connect( m_media.data(), SIGNAL(finished()), SLOT(slotFinished())); connect( m_media.data(), SIGNAL(aboutToFinish()), SLOT(slotAboutToFinish()) ); connect( m_media.data(), SIGNAL(metaDataChanged()), SLOT(slotMetaDataChanged()) ); connect( m_media.data(), SIGNAL(stateChanged(Phonon::State,Phonon::State)), SLOT(slotStateChanged(Phonon::State,Phonon::State)) ); connect( m_media.data(), SIGNAL(tick(qint64)), SLOT(slotTick(qint64)) ); connect( m_media.data(), SIGNAL(totalTimeChanged(qint64)), SLOT(slotTrackLengthChanged(qint64)) ); connect( m_media.data(), SIGNAL(currentSourceChanged(Phonon::MediaSource)), SLOT(slotNewTrackPlaying(Phonon::MediaSource)) ); connect( m_media.data(), SIGNAL(seekableChanged(bool)), SLOT(slotSeekableChanged(bool)) ); connect( m_audio.data(), SIGNAL(volumeChanged(qreal)), SLOT(slotVolumeChanged(qreal)) ); connect( m_audio.data(), SIGNAL(mutedChanged(bool)), SLOT(slotMutedChanged(bool)) ); connect( m_audioDataOutput.data(), SIGNAL(dataReady(QMap >)), SIGNAL(audioDataReady(QMap >)) ); connect( m_controller.data(), SIGNAL(titleChanged(int)), SLOT(slotTitleChanged(int)) ); // Read the volume from phonon m_volume = qBound( 0, qRound(m_audio.data()->volume()*100), 100 ); if( m_currentTrack ) { unsubscribeFrom( m_currentTrack ); m_currentTrack.clear(); } if( m_currentAlbum ) { unsubscribeFrom( m_currentAlbum ); m_currentAlbum.clear(); } } ////////////////////////////////////////////////////////////////////////////////////////// // PUBLIC ////////////////////////////////////////////////////////////////////////////////////////// QStringList EngineController::supportedMimeTypes() { // this ensures that slotFillInSupportedMimeTypes() is called in the main thread. It // will be called directly if we are called in the main thread (so that no deadlock // can occur) and indirectly if we are called in non-main thread. emit fillInSupportedMimeTypes(); // ensure slotFillInSupportedMimeTypes() called above has already finished: m_supportedMimeTypesSemaphore.acquire(); return m_supportedMimeTypes; } void EngineController::slotFillInSupportedMimeTypes() { // we assume non-empty == already filled in if( !m_supportedMimeTypes.isEmpty() ) { // unblock waiting for the semaphore in supportedMimeTypes(): m_supportedMimeTypesSemaphore.release(); return; } QRegExp avFilter( "^(audio|video)/", Qt::CaseInsensitive ); m_supportedMimeTypes = Phonon::BackendCapabilities::availableMimeTypes().filter( avFilter ); // Add whitelist hacks // MP4 Audio Books have a different extension that KFileItem/Phonon don't grok if( !m_supportedMimeTypes.contains( "audio/x-m4b" ) ) m_supportedMimeTypes << "audio/x-m4b"; // technically, "audio/flac" is not a valid mimetype (not on IANA list), but some things expect it if( m_supportedMimeTypes.contains( "audio/x-flac" ) && !m_supportedMimeTypes.contains( "audio/flac" ) ) m_supportedMimeTypes << "audio/flac"; // technically, "audio/mp4" is the official mime type, but sometimes Phonon returns audio/x-m4a if( m_supportedMimeTypes.contains( "audio/x-m4a" ) && !m_supportedMimeTypes.contains( "audio/mp4" ) ) m_supportedMimeTypes << "audio/mp4"; // unblock waiting for the semaphore in supportedMimeTypes(). We can over-shoot // resource number so that next call to supportedMimeTypes won't have to // wait for main loop; this is however just an optimization and we could have safely // released just one resource. Note that this code-path is reached only once, so // overflow cannot happen. m_supportedMimeTypesSemaphore.release( 100000 ); } void EngineController::restoreSession() { //here we restore the session //however, do note, this is always done, KDE session management is not involved if( AmarokConfig::resumePlayback() ) { - const KUrl url = AmarokConfig::resumeTrack(); + const QUrl url = AmarokConfig::resumeTrack(); Meta::TrackPtr track = CollectionManager::instance()->trackForUrl( url ); // Only give a resume time for local files, because resuming remote protocols can have weird side effects. // See: http://bugs.kde.org/show_bug.cgi?id=172897 if( url.isLocalFile() ) play( track, AmarokConfig::resumeTime(), AmarokConfig::resumePaused() ); else play( track, 0, AmarokConfig::resumePaused() ); } } void EngineController::endSession() { //only update song stats, when we're not going to resume it if ( !AmarokConfig::resumePlayback() && m_currentTrack ) { emit stopped( trackPositionMs(), m_currentTrack->length() ); unsubscribeFrom( m_currentTrack ); if( m_currentAlbum ) unsubscribeFrom( m_currentAlbum ); emit trackChanged( Meta::TrackPtr( 0 ) ); } emit sessionEnded( AmarokConfig::resumePlayback() && m_currentTrack ); } EqualizerController* EngineController::equalizerController() const { return m_equalizerController; } ////////////////////////////////////////////////////////////////////////////////////////// // PUBLIC SLOTS ////////////////////////////////////////////////////////////////////////////////////////// void EngineController::play() //SLOT { DEBUG_BLOCK if( isPlaying() ) return; if( isPaused() ) { if( m_currentTrack && m_currentTrack->type() == "stream" ) { debug() << "This is a stream that cannot be resumed after pausing. Restarting instead."; play( m_currentTrack ); return; } else { m_pauseTimer->stop(); if( supportsFadeout() ) m_fader.data()->setVolume( 1.0 ); m_media.data()->play(); emit trackPlaying( m_currentTrack ); return; } } The::playlistActions()->play(); } void EngineController::play( Meta::TrackPtr track, uint offset, bool startPaused ) { DEBUG_BLOCK if( !track ) // Guard return; // clear the current track without sending playbackEnded or trackChangeNotify yet stop( /* forceInstant */ true, /* playingWillContinue */ true ); // we grant exclusive access to setting new m_currentTrack to newTrackPlaying() m_nextTrack = track; debug() << "play: bounded is "<name(); m_boundedPlayback = track->create(); m_multiPlayback = track->create(); track->prepareToPlay(); m_nextUrl = track->playableUrl(); if( m_multiPlayback ) { - connect( m_multiPlayback, SIGNAL(playableUrlFetched(KUrl)), - SLOT(slotPlayableUrlFetched(KUrl)) ); + connect( m_multiPlayback, SIGNAL(playableUrlFetched(QUrl)), + SLOT(slotPlayableUrlFetched(QUrl)) ); m_multiPlayback->fetchFirst(); } else if( m_boundedPlayback ) { debug() << "Starting bounded playback of url " << track->playableUrl() << " at position " << m_boundedPlayback->startPosition(); playUrl( track->playableUrl(), m_boundedPlayback->startPosition(), startPaused ); } else { debug() << "Just a normal, boring track... :-P"; playUrl( track->playableUrl(), offset, startPaused ); } } void EngineController::replay() // slot { DEBUG_BLOCK seekTo( 0 ); emit trackPositionChanged( 0, true ); } void -EngineController::playUrl( const KUrl &url, uint offset, bool startPaused ) +EngineController::playUrl( const QUrl &url, uint offset, bool startPaused ) { DEBUG_BLOCK m_media.data()->stop(); debug() << "URL: " << url << url.url(); debug() << "Offset: " << offset; m_currentAudioCdTrack = 0; - if( url.protocol() == "audiocd" ) + if( url.scheme() == "audiocd" ) { QStringList pathItems = url.path().split( '/', QString::KeepEmptyParts ); if( pathItems.count() != 3 ) { error() << __PRETTY_FUNCTION__ << url.url() << "is not in expected format"; return; } bool ok = false; int trackNumber = pathItems.at( 2 ).toInt( &ok ); if( !ok || trackNumber <= 0 ) { error() << __PRETTY_FUNCTION__ << "failed to get positive track number from" << url.url(); return; } - QString device = url.queryItem( "device" ); + QString device = QUrlQuery(url).queryItemValue( "device" ); m_media.data()->setCurrentSource( Phonon::MediaSource( Phonon::Cd, device ) ); m_currentAudioCdTrack = trackNumber; } else { // keep in sync with setNextTrack(), slotPlayableUrlFetched() if( url.isLocalFile() ) m_media.data()->setCurrentSource( url.toLocalFile() ); else m_media.data()->setCurrentSource( url ); } m_media.data()->clearQueue(); if( m_currentAudioCdTrack ) { // call to play() is asynchronous and ->setCurrentTitle() can be only called on // playing, buffering or paused media. m_media.data()->pause(); DelayedTrackChanger *trackChanger = new DelayedTrackChanger( m_media.data(), m_controller.data(), m_currentAudioCdTrack, offset, startPaused ); connect( trackChanger, SIGNAL(trackPositionChanged(qint64,bool)), SIGNAL(trackPositionChanged(qint64,bool)) ); } else if( offset ) { // call to play() is asynchronous and ->seek() can be only called on playing, // buffering or paused media. Calling play() would lead to audible glitches, // so call pause() that doesn't suffer from such problem. m_media.data()->pause(); DelayedSeeker *seeker = new DelayedSeeker( m_media.data(), offset, startPaused ); connect( seeker, SIGNAL(trackPositionChanged(qint64,bool)), SIGNAL(trackPositionChanged(qint64,bool)) ); } else { if( startPaused ) { m_media.data()->pause(); } else { m_pauseTimer->stop(); if( supportsFadeout() ) m_fader.data()->setVolume( 1.0 ); m_media.data()->play(); } } } void EngineController::pause() //SLOT { if( supportsFadeout() && AmarokConfig::fadeoutOnPause() ) { m_fader.data()->fadeOut( AmarokConfig::fadeoutLength() ); m_pauseTimer->start( AmarokConfig::fadeoutLength() + 500 ); return; } slotPause(); } void EngineController::slotPause() { if( supportsFadeout() && AmarokConfig::fadeoutOnPause() ) { // Reset VolumeFaderEffect to full volume m_fader.data()->setVolume( 1.0 ); // Wait a bit before pausing the pipeline. Necessary for the new fader setting to take effect. QTimer::singleShot( 1000, m_media.data(), SLOT(pause()) ); } else { m_media.data()->pause(); } emit paused(); } void EngineController::stop( bool forceInstant, bool playingWillContinue ) //SLOT { DEBUG_BLOCK /* Only do fade-out when all conditions are met: * a) instant stop is not requested * b) we aren't already in a fadeout * c) we are currently playing (not paused etc.) * d) Amarok is configured to fadeout at all * e) configured fadeout length is positive * f) Phonon fader to do it is actually available */ bool doFadeOut = !forceInstant && !m_fadeouter && m_media.data()->state() == Phonon::PlayingState && AmarokConfig::fadeoutOnStop() && AmarokConfig::fadeoutLength() > 0 && m_fader; // let Amarok know that the previous track is no longer playing; if we will fade-out // ::stop() is called after the fade by Fadeouter. if( m_currentTrack && !doFadeOut ) { unsubscribeFrom( m_currentTrack ); if( m_currentAlbum ) unsubscribeFrom( m_currentAlbum ); const qint64 pos = trackPositionMs(); // updateStreamLength() intentionally not here, we're probably in the middle of a track const qint64 length = trackLength(); emit trackFinishedPlaying( m_currentTrack, pos / qMax( length, pos ) ); m_currentTrack = 0; m_currentAlbum = 0; if( !playingWillContinue ) { emit stopped( pos, length ); emit trackChanged( m_currentTrack ); } } { QMutexLocker locker( &m_mutex ); delete m_boundedPlayback; m_boundedPlayback = 0; delete m_multiPlayback; // need to get a new instance of multi if played again m_multiPlayback = 0; m_multiSource.reset(); m_nextTrack.clear(); m_nextUrl.clear(); m_media.data()->clearQueue(); } if( doFadeOut ) { m_fadeouter = new Fadeouter( m_media, m_fader, AmarokConfig::fadeoutLength() ); // even though we don't pass forceInstant, doFadeOut will be false because // m_fadeouter will be still valid connect( m_fadeouter.data(), SIGNAL(fadeoutFinished()), SLOT(stop()) ); } else { m_media.data()->stop(); m_media.data()->setCurrentSource( Phonon::MediaSource() ); } } bool EngineController::isPaused() const { return m_media.data()->state() == Phonon::PausedState; } bool EngineController::isPlaying() const { return !isPaused() && !isStopped(); } bool EngineController::isStopped() const { return m_media.data()->state() == Phonon::StoppedState || m_media.data()->state() == Phonon::LoadingState || m_media.data()->state() == Phonon::ErrorState; } void EngineController::playPause() //SLOT { DEBUG_BLOCK debug() << "PlayPause: EngineController state" << m_media.data()->state(); if( isPlaying() ) pause(); else play(); } void EngineController::seekTo( int ms ) //SLOT { DEBUG_BLOCK if( m_media.data()->isSeekable() ) { debug() << "seek to: " << ms; int seekTo; if( m_boundedPlayback ) { seekTo = m_boundedPlayback->startPosition() + ms; if( seekTo < m_boundedPlayback->startPosition() ) seekTo = m_boundedPlayback->startPosition(); else if( seekTo > m_boundedPlayback->startPosition() + trackLength() ) seekTo = m_boundedPlayback->startPosition() + trackLength(); } else seekTo = ms; m_media.data()->seek( static_cast( seekTo ) ); emit trackPositionChanged( seekTo, true ); /* User seek */ } else debug() << "Stream is not seekable."; } void EngineController::seekBy( int ms ) //SLOT { qint64 newPos = m_media.data()->currentTime() + ms; seekTo( newPos <= 0 ? 0 : newPos ); } int EngineController::increaseVolume( int ticks ) //SLOT { return setVolume( volume() + ticks ); } int EngineController::decreaseVolume( int ticks ) //SLOT { return setVolume( volume() - ticks ); } int EngineController::setVolume( int percent ) //SLOT { percent = qBound( 0, percent, 100 ); m_volume = percent; const qreal volume = percent / 100.0; if ( !m_ignoreVolumeChangeAction && m_audio.data()->volume() != volume ) { m_ignoreVolumeChangeObserve = true; m_audio.data()->setVolume( volume ); AmarokConfig::setMasterVolume( percent ); emit volumeChanged( percent ); } m_ignoreVolumeChangeAction = false; return percent; } int EngineController::volume() const { return m_volume; } bool EngineController::isMuted() const { return m_audio.data()->isMuted(); } void EngineController::setMuted( bool mute ) //SLOT { m_audio.data()->setMuted( mute ); // toggle mute if( !isMuted() ) setVolume( m_volume ); AmarokConfig::setMuteState( mute ); emit muteStateChanged( mute ); } void EngineController::toggleMute() //SLOT { setMuted( !isMuted() ); } Meta::TrackPtr EngineController::currentTrack() const { return m_currentTrack; } qint64 EngineController::trackLength() const { //When starting a last.fm stream, Phonon still shows the old track's length--trust //Meta::Track over Phonon if( m_currentTrack && m_currentTrack->length() > 0 ) return m_currentTrack->length(); else return m_media.data()->totalTime(); //may return -1 } void EngineController::setNextTrack( Meta::TrackPtr track ) { DEBUG_BLOCK if( !track ) return; track->prepareToPlay(); - KUrl url = track->playableUrl(); + QUrl url = track->playableUrl(); if( url.isEmpty() ) return; QMutexLocker locker( &m_mutex ); if( isPlaying() ) { m_media.data()->clearQueue(); // keep in sync with playUrl(), slotPlayableUrlFetched() if( url.isLocalFile() ) m_media.data()->enqueue( url.toLocalFile() ); - else if( url.protocol() != "audiocd" ) // we don't support gapless for CD, bug 305708 + else if( url.scheme() != "audiocd" ) // we don't support gapless for CD, bug 305708 m_media.data()->enqueue( url ); m_nextTrack = track; m_nextUrl = url; } else play( track ); } bool EngineController::isStream() { Phonon::MediaSource::Type type = Phonon::MediaSource::Invalid; if( m_media ) // type is determined purely from the MediaSource constructor used in - // setCurrentSource(). For streams we use the KUrl one, see playUrl() + // setCurrentSource(). For streams we use the QUrl one, see playUrl() type = m_media.data()->currentSource().type(); return type == Phonon::MediaSource::Url || type == Phonon::MediaSource::Stream; } bool EngineController::isSeekable() const { if( m_media ) return m_media.data()->isSeekable(); return false; } int EngineController::trackPosition() const { return trackPositionMs() / 1000; } qint64 EngineController::trackPositionMs() const { return m_media.data()->currentTime(); } bool EngineController::supportsFadeout() const { return m_fader; } bool EngineController::supportsGainAdjustments() const { return m_preamp; } bool EngineController::supportsAudioDataOutput() const { const Phonon::AudioDataOutput out; return out.isValid(); } ////////////////////////////////////////////////////////////////////////////////////////// // PRIVATE SLOTS ////////////////////////////////////////////////////////////////////////////////////////// void EngineController::slotTick( qint64 position ) { if( m_boundedPlayback ) { qint64 newPosition = position; emit trackPositionChanged( static_cast( position - m_boundedPlayback->startPosition() ), false ); // Calculate a better position. Sometimes the position doesn't update // with a good resolution (for example, 1 sec for TrueAudio files in the // Xine-1.1.18 backend). This tick function, in those cases, just gets // called multiple times with the same position. We count how many // times this has been called prior, and adjust for it. if( position == m_lastTickPosition ) newPosition += ++m_lastTickCount * m_tickInterval; else m_lastTickCount = 0; m_lastTickPosition = position; //don't go beyond the stop point if( newPosition >= m_boundedPlayback->endPosition() ) { slotAboutToFinish(); } } else { m_lastTickPosition = position; emit trackPositionChanged( static_cast( position ), false ); } } void EngineController::slotAboutToFinish() { DEBUG_BLOCK if( m_fadeouter ) { debug() << "slotAboutToFinish(): a fadeout is in progress, don't queue new track"; return; } if( m_multiPlayback ) { DEBUG_LINE_INFO m_mutex.lock(); m_playWhenFetched = false; m_mutex.unlock(); m_multiPlayback->fetchNext(); debug() << "The queue has: " << m_media.data()->queue().size() << " tracks in it"; } else if( m_multiSource ) { debug() << "source finished, lets get the next one"; - KUrl nextSource = m_multiSource->nextUrl(); + QUrl nextSource = m_multiSource->nextUrl(); if( !nextSource.isEmpty() ) { //more sources m_mutex.lock(); m_playWhenFetched = false; m_mutex.unlock(); debug() << "playing next source: " << nextSource; slotPlayableUrlFetched( nextSource ); } else if( m_media.data()->queue().isEmpty() ) { debug() << "no more sources, skip to next track"; m_multiSource.reset(); // don't cofuse slotFinished The::playlistActions()->requestNextTrack(); } } else if( m_boundedPlayback ) { debug() << "finished a track that consists of part of another track, go to next track even if this url is technically not done yet"; //stop this track, now, as the source track might go on and on, and //there might not be any more tracks in the playlist... stop( true ); The::playlistActions()->requestNextTrack(); } else if( m_media.data()->queue().isEmpty() ) The::playlistActions()->requestNextTrack(); } void EngineController::slotFinished() { DEBUG_BLOCK // paranoia checking, m_currentTrack shouldn't really be null if( m_currentTrack ) { debug() << "Track finished completely, updating statistics"; unsubscribeFrom( m_currentTrack ); // don't bother with trackMetadataChanged() stampStreamTrackLength(); // update track length in stream for accurate scrobbling emit trackFinishedPlaying( m_currentTrack, 1.0 ); subscribeTo( m_currentTrack ); } if( !m_multiPlayback && !m_multiSource ) { // again. at this point the track is finished so it's trackPositionMs is 0 if( !m_nextTrack && m_nextUrl.isEmpty() ) emit stopped( m_currentTrack ? m_currentTrack->length() : 0, m_currentTrack ? m_currentTrack->length() : 0 ); unsubscribeFrom( m_currentTrack ); if( m_currentAlbum ) unsubscribeFrom( m_currentAlbum ); m_currentTrack = 0; m_currentAlbum = 0; if( !m_nextTrack && m_nextUrl.isEmpty() ) // we will the trackChanged signal later emit trackChanged( Meta::TrackPtr() ); m_media.data()->setCurrentSource( Phonon::MediaSource() ); } m_mutex.lock(); // in case setNextTrack is being handled right now. // Non-local urls are not enqueued so we must play them explicitly. if( m_nextTrack ) { DEBUG_LINE_INFO play( m_nextTrack ); } else if( !m_nextUrl.isEmpty() ) { DEBUG_LINE_INFO playUrl( m_nextUrl, 0 ); } else { DEBUG_LINE_INFO // possibly we are waiting for a fetch m_playWhenFetched = true; } m_mutex.unlock(); } static const qreal log10over20 = 0.1151292546497022842; // ln(10) / 20 void EngineController::slotNewTrackPlaying( const Phonon::MediaSource &source ) { DEBUG_BLOCK if( source.type() == Phonon::MediaSource::Empty ) { debug() << "Empty MediaSource (engine stop)"; return; } if( m_currentTrack ) { unsubscribeFrom( m_currentTrack ); if( m_currentAlbum ) unsubscribeFrom( m_currentAlbum ); } // only update stats if we are called for something new, some phonon back-ends (at // least phonon-gstreamer-4.6.1) call slotNewTrackPlaying twice with the same source if( m_currentTrack && ( m_nextTrack || !m_nextUrl.isEmpty() ) ) { debug() << "Previous track finished completely, updating statistics"; stampStreamTrackLength(); // update track length in stream for accurate scrobbling emit trackFinishedPlaying( m_currentTrack, 1.0 ); if( m_multiSource ) // advance source of a multi-source track m_multiSource->setSource( m_multiSource->current() + 1 ); } m_nextUrl.clear(); if( m_nextTrack ) { // already unsubscribed m_currentTrack = m_nextTrack; m_nextTrack.clear(); m_multiSource.reset( m_currentTrack->create() ); if( m_multiSource ) { debug() << "Got a MultiSource Track with" << m_multiSource->sources().count() << "sources"; - connect( m_multiSource.data(), SIGNAL(urlChanged(KUrl)), - SLOT(slotPlayableUrlFetched(KUrl)) ); + connect( m_multiSource.data(), SIGNAL(urlChanged(QUrl)), + SLOT(slotPlayableUrlFetched(QUrl)) ); } } if( m_currentTrack && AmarokConfig::replayGainMode() != AmarokConfig::EnumReplayGainMode::Off ) { Meta::ReplayGainTag mode; // gain is usually negative (but may be positive) mode = ( AmarokConfig::replayGainMode() == AmarokConfig::EnumReplayGainMode::Track) ? Meta::ReplayGain_Track_Gain : Meta::ReplayGain_Album_Gain; qreal gain = m_currentTrack->replayGain( mode ); // peak is usually positive and smaller than gain (but may be negative) mode = ( AmarokConfig::replayGainMode() == AmarokConfig::EnumReplayGainMode::Track) ? Meta::ReplayGain_Track_Peak : Meta::ReplayGain_Album_Peak; qreal peak = m_currentTrack->replayGain( mode ); if( gain + peak > 0.0 ) { debug() << "Gain of" << gain << "would clip at absolute peak of" << gain + peak; gain -= gain + peak; } if( m_preamp ) { debug() << "Using gain of" << gain << "with relative peak of" << peak; // we calculate the volume change ourselves, because m_preamp.data()->setVolumeDecibel is // a little confused about minus signs m_preamp.data()->setVolume( qExp( gain * log10over20 ) ); } else warning() << "Would use gain of" << gain << ", but current Phonon backend" << "doesn't seem to support pre-amplifier (VolumeFaderEffect)"; } else if( m_preamp ) { m_preamp.data()->setVolume( 1.0 ); } bool useTrackWithinStreamDetection = false; if( m_currentTrack ) { subscribeTo( m_currentTrack ); Meta::AlbumPtr m_currentAlbum = m_currentTrack->album(); if( m_currentAlbum ) subscribeTo( m_currentAlbum ); /** We only use detect-tracks-in-stream for tracks that have stream type * (exactly, we purposely exclude stream/lastfm) *and* that don't have length * already filled in. Bug 311852 */ if( m_currentTrack->type() == "stream" && m_currentTrack->length() == 0 ) useTrackWithinStreamDetection = true; } m_lastStreamStampPosition = useTrackWithinStreamDetection ? 0 : -1; emit trackChanged( m_currentTrack ); emit trackPlaying( m_currentTrack ); } void EngineController::slotStateChanged( Phonon::State newState, Phonon::State oldState ) //SLOT { debug() << "slotStateChanged from" << oldState << "to" << newState; static const int maxErrors = 5; static int errorCount = 0; // Sanity checks: if( newState == oldState ) return; if( newState == Phonon::ErrorState ) // If media is borked, skip to next track { emit trackError( m_currentTrack ); warning() << "Phonon failed to play this URL. Error: " << m_media.data()->errorString(); warning() << "Forcing phonon engine reinitialization."; /* In case of error Phonon MediaObject automatically switches to KioMediaSource, which cause problems: runs StopAfterCurrentTrack mode, force PlayPause button to reply the track (can't be paused). So we should reinitiate Phonon after each Error. */ initializePhonon(); errorCount++; if ( errorCount >= maxErrors ) { // reset error count errorCount = 0; Amarok::Components::logger()->longMessage( i18n( "Too many errors encountered in playlist. Playback stopped." ), Amarok::Logger::Warning ); error() << "Stopping playlist."; } else // and start the next song, even if the current failed to start playing The::playlistActions()->requestUserNextTrack(); } else if( newState == Phonon::PlayingState ) { errorCount = 0; emit playbackStateChanged(); } else if( newState == Phonon::StoppedState || newState == Phonon::PausedState ) { emit playbackStateChanged(); } } void -EngineController::slotPlayableUrlFetched( const KUrl &url ) +EngineController::slotPlayableUrlFetched( const QUrl &url ) { DEBUG_BLOCK debug() << "Fetched url: " << url; if( url.isEmpty() ) { DEBUG_LINE_INFO The::playlistActions()->requestNextTrack(); return; } if( !m_playWhenFetched ) { DEBUG_LINE_INFO m_mutex.lock(); m_media.data()->clearQueue(); // keep synced with setNextTrack(), playUrl() if( url.isLocalFile() ) m_media.data()->enqueue( url.toLocalFile() ); else m_media.data()->enqueue( url ); m_nextTrack.clear(); m_nextUrl = url; debug() << "The next url we're playing is: " << m_nextUrl; // reset this flag each time m_playWhenFetched = true; m_mutex.unlock(); } else { DEBUG_LINE_INFO m_mutex.lock(); playUrl( url, 0 ); m_mutex.unlock(); } } void EngineController::slotTrackLengthChanged( qint64 milliseconds ) { debug() << "slotTrackLengthChanged(" << milliseconds << ")"; emit trackLengthChanged( ( !m_multiPlayback || !m_boundedPlayback ) ? trackLength() : milliseconds ); } void EngineController::slotMetaDataChanged() { QVariantMap meta; meta.insert( Meta::Field::URL, m_media.data()->currentSource().url() ); static const QList fieldPairs = QList() << FieldPair( Phonon::ArtistMetaData, Meta::Field::ARTIST ) << FieldPair( Phonon::AlbumMetaData, Meta::Field::ALBUM ) << FieldPair( Phonon::TitleMetaData, Meta::Field::TITLE ) << FieldPair( Phonon::GenreMetaData, Meta::Field::GENRE ) << FieldPair( Phonon::TracknumberMetaData, Meta::Field::TRACKNUMBER ) << FieldPair( Phonon::DescriptionMetaData, Meta::Field::COMMENT ); foreach( const FieldPair &pair, fieldPairs ) { QStringList values = m_media.data()->metaData( pair.first ); if( !values.isEmpty() ) meta.insert( pair.second, values.first() ); } // note: don't rely on m_currentTrack here. At least some Phonon backends first emit // totalTimeChanged(), then metaDataChanged() and only then currentSourceChanged() // which currently sets correct m_currentTrack. if( isInRecentMetaDataHistory( meta ) ) { // slotMetaDataChanged() triggered by phonon, but we've already seen // exactly the same metadata recently. Ignoring for now. return; } // following is an implementation of song end (and length) within a stream detection. // This normally fires minutes after the track has started playing so m_currentTrack // should be accurate if( m_currentTrack && m_lastStreamStampPosition >= 0 ) { stampStreamTrackLength(); emit trackFinishedPlaying( m_currentTrack, 1.0 ); // update track length to 0 because length emitted by stampStreamTrackLength() // is for the previous song meta.insert( Meta::Field::LENGTH, 0 ); } debug() << "slotMetaDataChanged(): new meta-data:" << meta; emit currentMetadataChanged( meta ); } void EngineController::slotSeekableChanged( bool seekable ) { emit seekableChanged( seekable ); } void EngineController::slotTitleChanged( int titleNumber ) { DEBUG_BLOCK if ( titleNumber != m_currentAudioCdTrack ) { The::playlistActions()->requestNextTrack(); slotFinished(); } } void EngineController::slotVolumeChanged( qreal newVolume ) { int percent = qBound( 0, qRound(newVolume * 100), 100 ); if ( !m_ignoreVolumeChangeObserve && m_volume != percent ) { m_ignoreVolumeChangeAction = true; m_volume = percent; AmarokConfig::setMasterVolume( percent ); emit volumeChanged( percent ); } else m_volume = percent; m_ignoreVolumeChangeObserve = false; } void EngineController::slotMutedChanged( bool mute ) { AmarokConfig::setMuteState( mute ); emit muteStateChanged( mute ); } void EngineController::slotTrackFinishedPlaying( Meta::TrackPtr track, double playedFraction ) { Q_ASSERT( track ); debug() << "slotTrackFinishedPlaying(" << ( track->artist() ? track->artist()->name() : QString( "[no artist]" ) ) << "-" << ( track->album() ? track->album()->name() : QString( "[no album]" ) ) << "-" << track->name() << "," << playedFraction << ")"; track->finishedPlaying( playedFraction ); } void EngineController::metadataChanged( Meta::TrackPtr track ) { Meta::AlbumPtr album = m_currentTrack->album(); if( m_currentAlbum != album ) { if( m_currentAlbum ) unsubscribeFrom( m_currentAlbum ); m_currentAlbum = album; if( m_currentAlbum ) subscribeTo( m_currentAlbum ); } emit trackMetadataChanged( track ); } void EngineController::metadataChanged( Meta::AlbumPtr album ) { emit albumMetadataChanged( album ); } QString EngineController::prettyNowPlaying( bool progress ) const { Meta::TrackPtr track = currentTrack(); if( track ) { QString title = Qt::escape( track->name() ); QString prettyTitle = Qt::escape( track->prettyName() ); QString artist = track->artist() ? Qt::escape( track->artist()->name() ) : QString(); QString album = track->album() ? Qt::escape( track->album()->name() ) : QString(); // ugly because of translation requirements if( !title.isEmpty() && !artist.isEmpty() && !album.isEmpty() ) title = i18nc( "track by artist on album", "%1 by %2 on %3", title, artist, album ); else if( !title.isEmpty() && !artist.isEmpty() ) title = i18nc( "track by artist", "%1 by %2", title, artist ); else if( !album.isEmpty() ) // we try for pretty title as it may come out better title = i18nc( "track on album", "%1 on %2", prettyTitle, album ); else title = "" + prettyTitle + ""; if( title.isEmpty() ) title = i18n( "Unknown track" ); QScopedPointer sic( track->create() ); if( sic ) { QString source = sic->sourceName(); if( !source.isEmpty() ) title += ' ' + i18nc( "track from source", "from %1", source ); } if( track->length() > 0 ) { QString length = Qt::escape( Meta::msToPrettyTime( track->length() ) ); title += " ("; if( progress ) title+= Qt::escape( Meta::msToPrettyTime( m_lastTickPosition ) ) + '/'; title += length + ')'; } return title; } else return i18n( "No track playing" ); } bool EngineController::isInRecentMetaDataHistory( const QVariantMap &meta ) { // search for Metadata in history for( int i = 0; i < m_metaDataHistory.size(); i++) { if( m_metaDataHistory.at( i ) == meta ) // we already had that one -> spam! { m_metaDataHistory.move( i, 0 ); // move spam to the beginning of the list return true; } } if( m_metaDataHistory.size() == 12 ) m_metaDataHistory.removeLast(); m_metaDataHistory.insert( 0, meta ); return false; } void EngineController::stampStreamTrackLength() { if( m_lastStreamStampPosition < 0 ) return; qint64 currentPosition = trackPositionMs(); debug() << "stampStreamTrackLength(): m_lastStreamStampPosition:" << m_lastStreamStampPosition << "currentPosition:" << currentPosition; if( currentPosition == m_lastStreamStampPosition ) return; qint64 length = qMax( currentPosition - m_lastStreamStampPosition, qint64( 0 ) ); updateStreamLength( length ); m_lastStreamStampPosition = currentPosition; } void EngineController::updateStreamLength( qint64 length ) { if( !m_currentTrack ) { warning() << __PRETTY_FUNCTION__ << "called with cull m_currentTrack"; return; } // Last.fm scrobbling needs to know track length before it can scrobble: QVariantMap lengthMetaData; // we cannot use m_media->currentSource()->url() here because it is already empty, bug 309976 lengthMetaData.insert( Meta::Field::URL, QUrl( m_currentTrack->playableUrl() ) ); lengthMetaData.insert( Meta::Field::LENGTH, length ); debug() << "updateStreamLength(): emitting currentMetadataChanged(" << lengthMetaData << ")"; emit currentMetadataChanged( lengthMetaData ); } diff --git a/src/EngineController.h b/src/EngineController.h index 7342867b84..9da26fc53b 100644 --- a/src/EngineController.h +++ b/src/EngineController.h @@ -1,576 +1,576 @@ /**************************************************************************************** * Copyright (c) 2004 Frederik Holljen * * Copyright (c) 2004,2005 Max Howell * * Copyright (c) 2004-2013 Mark Kretschmann * * Copyright (c) 2008 Jason A. Donenfeld * * Copyright (c) 2009 Artur Szymiec * * * * 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, see . * ****************************************************************************************/ #ifndef AMAROK_ENGINECONTROLLER_H #define AMAROK_ENGINECONTROLLER_H #include "amarok_export.h" #include "core/capabilities/BoundedPlaybackCapability.h" #include "core/meta/Observer.h" #include "playback/EqualizerController.h" -#include +#include #include #include #include #include #include #include #include #include #include #include #include class Fadeouter; namespace Capabilities { class MultiPlayableCapability; class MultiSourceCapability; } namespace Phonon { class AudioOutput; class MediaSource; class VolumeFaderEffect; } class QTimer; /** * A thin wrapper around Phonon that implements Amarok-specific functionality like * replay gain, fade-out on stop and various track capabilities that affect * playback. */ class AMAROK_EXPORT EngineController : public QObject, public Meta::Observer { Q_OBJECT public: static const uint DATAOUTPUT_DATA_SIZE = 512; /** * Construct EngineController. Must be called from the main thread. */ EngineController(); ~EngineController(); /** * Returns the global EngineController instance */ static EngineController* instance(); /** * Loads and plays the track that was playing when endSession() was last * called (ie: when Amarok was quit) */ void restoreSession(); /** * Saves the currently playing track and the playing/paused/stopped state */ void endSession(); /** * Returns a list of backend supported mime types. This method is thread-safe. */ QStringList supportedMimeTypes(); /** @return track position (elapsed time) in seconds */ int trackPosition() const; /** @return track position (elapsed time) in milliseconds */ qint64 trackPositionMs() const; /** * Returns the current track that is loaded into the engine. * @return a Meta::TrackPtr which is either the track, or empty if phonon * has a state of Phonon::ErrorState or Phonon::StoppedState */ Meta::TrackPtr currentTrack() const; /** * @return the length of the current track in milliseconds */ qint64 trackLength() const; /** * Used to enqueue a track before it starts to play, for gapless playback. * * This will clear any tracks currently in the queue. If no track is playing, * @p track will be played immediately. */ void setNextTrack( Meta::TrackPtr track ); /** * Gets the volume * @return the volume as a percentage */ int volume() const; /** * @return @c true if sound output is disabled, @false otherwise */ bool isMuted() const; /** * @return @c true if Amarok is paused, @c false if it is stopped or playing */ bool isPaused() const; /** * @return @c true if Amarok is playing, @c false if it is stopped or pause * Note: A fading out track is considered already stopped. */ bool isPlaying() const; /** * @return @c true if Amarok is stopped, @c false if it is playing or pause * Note: A fading out track is considered already stopped. */ bool isStopped() const; /** * Streams sometimes have to be treated specially. * For example, it is typically not possible to rewind a stream (at least, * not without returning to the start of it). * However for rewinding we have isSeekable(). * Also for streams usually the meta data received by currentTrack() is only * for the whole stream while the meta data received by currentMetaDataChanged * will be more current (or contain advertisment) * * @return @c true if the current track is a stream, @c false otherwise */ bool isStream(); /** * @return @c true if the current track is seekable, @c false otherwise */ bool isSeekable() const; /** * Returns the associated EqualizerController object. */ EqualizerController *equalizerController() const; /** * @return QString with a pretty name for the current track * @param whether to include the playing progress (default false) */ QString prettyNowPlaying( bool progress = false ) const; public slots: /** * Plays the current track, if there is one * This happens asynchronously. */ void play(); /** * Plays the specified track * This happens asynchronously. */ void play( Meta::TrackPtr track, uint offset = 0, bool startPaused = false ); /** * Replays the current track * * This is a convenience method, calls seek( 0 ) actually */ void replay(); /** * Pauses the current track * This happens asynchronously. */ void pause(); /** * Stops playing * This happens asynchronously. * * @param forceInstant skip any fade-out effects * @param playingWillContinue don't emit stopped() or trackChanged( 0 ) signals */ void stop( bool forceInstant = false, bool playingWillContinue = false ); /** * Pauses if Amarok is currently playing, plays if Amarok is stopped or paused * This happens asynchronously. */ void playPause(); //pauses if playing, plays if paused or stopped /** * Seeks to a position in the track * * If the media is not seekable, or the state is something other than * PlayingState, BufferingState or PausedState, has no effect. * * Deals correctly with tracks that have the BoundedPlayback capability. * * @param ms the position in milliseconds (counting from the start of the track) */ void seekTo( int ms ); /** * Seeks forward or backward in the track * * If the media is not seekable, or the state is something other than * PlayingState, BufferingState or PausedState, has no effect. * * Deals correctly with tracks that have the BoundedPlayback capability. * * A negative value seeks backwards, a positive value seeks forwards. * * If the value of @p ms would move the position to before the start of the track, * the position is moved to the start of the track. * * @param ms the offset from the current position in milliseconds */ void seekBy( int ms ); /** * Increases the volume * * @param ticks the amount to increase the volume by, given as a percentage of the * maximum possible volume (ie: the same units as for setVolume()). */ int increaseVolume( int ticks = 100/25 ); /** * Decreases the volume * * @param ticks the amount to decrease the volume by, given as a percentage of the * maximum possible volume (ie: the same units as for setVolume()). */ int decreaseVolume( int ticks = 100/25 ); /** * Sets the volume * * @param percent the new volume as a percentage of the maximum possible volume. */ // this amplifier does not go up to 11 int setVolume( int percent ); /** * Mutes or unmutes playback * * @param mute if @c true, audio output will be disabled; if @c false, audio output * will be enabled. */ void setMuted( bool mute ); /** * Toggles mute * * Works like setMuted( !isMuted() ); */ void toggleMute(); /** * Return true if current Phonon back-end supports fade-out. */ bool supportsFadeout() const; /** * Return true if current Phonon back-end supports our implementation of * Replay Gain adjustment. */ bool supportsGainAdjustments() const; /** * Return true if the current Phonon backend supports visualizations. */ bool supportsAudioDataOutput() const; Q_SIGNALS: /** * Emitted when the playback stops while playing a track. * This signal is not emitted when the track pauses or the playback stopps because * Amarok was closed and "resume at start" is configured. * It is also not emitted if the playback continues with another track. In such * a case you would just get another trackPlaying signal. * Both parameters are in milli seconds. */ void stopped( qint64 /*ms*/ finalPosition, qint64 /*ms*/ trackLength ); /** * Called when the playback is paused. * When the playback is resumed a trackPlaying signal will be emitted. * When the playback is stopped then a stopped signal will be emitted. */ void paused(); /** While trying to play the track an error occurred. * This usually means that the engine will try to play the next track in * the playlist until it gives up. * So you will get a trackPlaying or stopped signal next. */ void trackError( Meta::TrackPtr track ); /** * Called when a new track starts playing or an old track starts playing now. * * It also might be called several time in sequence * with the same track in cases when e.g. you have * a multi source track. * * Unlike trackChanged(), this is not called when playback stops. */ void trackPlaying( Meta::TrackPtr track ); /** * Called when the current track changes * * Note that this is possibly only called once in case of a stream or on * the other hand multiple times with the same track in cases when e.g. you have * a multi source track. * * Unlike trackPlaying(), this is called when playback stops with Meta::TrackPtr( 0 ) for @p track. * * @param track The new track; may be null */ void trackChanged( Meta::TrackPtr track ); /** * Emitted when the metadata of the current track changes. * * You might want to connect also to trackChanged() or trackPlaying() to get more * changes because this signal is only emitted when the track metadata changes * while it's playing, not when new track starts playing. This method now works * correctly also for streams and is preferred to currentMetaDataChanged() because * it providers somehow more filtered values. */ void trackMetadataChanged( Meta::TrackPtr track ); /** Emitted when the metadata of the current album changes. */ void albumMetadataChanged( Meta::AlbumPtr album ); /** * Emitted then the information for the current changed. * This signal contains data from Phonon about the meta data of the track or stream. * This signal is expecially emitted when a stream changes it's metadata. * This can happen e.g. in a ogg stream where the currentTrack data will probably * not be updated. * * MetaStream::Track::Private in Stream_p.h will connect to this signal to update it's internal data * and then itself trigger a trackMetadataChanged. * @param metadata Contains the url, artist, album title, title, genre, tracknumber and length */ void currentMetadataChanged( QVariantMap metadata ); /** * Called when the seekable value was changed */ void seekableChanged( bool seekable ); /** * Called when the volume was changed */ void volumeChanged( int percent ); /** * Called when audio output was enabled or disabled * * NB: if setMute() was called on the engine controller, but it didn't change the * mute state, this will not be called */ void muteStateChanged( bool mute ); /** Called when the track position changes. If the track just progresses you will get a notification every couple of milliseconds. @parem position The current position in milliseconds @param userSeek True if the position change was caused by the user */ void trackPositionChanged( qint64 position, bool userSeek ); /** * Emitted when a track finished playing. You generally get this signal once per * played track, but in case of a stream this may be emitted more than once when * stream meta-data changes (which usually indicates that the next track started * playing) - meta-data in the track are updated in this case. When you receive * this signal, track score, play count etc. will be already updated. * * @param track track that has just finished playing * @param playedFraction played/total length fraction, between 0 and 1 */ void trackFinishedPlaying( Meta::TrackPtr track, double playedFraction ); /** Called when the track length changes, typically because the track has changed but also when phonon manages to determine the full track length. */ void trackLengthChanged( qint64 milliseconds ); /** * Called when Amarok is closed and we disconnect from Phonon. * @param resumePlayback True if amarok will continue playback after a restart. */ void sessionEnded( bool resumePlayback ); /** * Called when playback state changes to PlayingState, StoppedState or PausedState. */ void playbackStateChanged(); /** * Is emitted when new audio Data is ready * @param audioData The audio data that is available */ void audioDataReady( const QMap > &audioData ); /** * A trick to call slotFillInSupportedMimeTypes() in a main thread, not to be used * anywhere else than in supportedMimeTypes(). */ void fillInSupportedMimeTypes(); private slots: /** * Sets up the Phonon system */ void initializePhonon(); /** This slot is connected to the phonon finished signal. It is emitted when the queue is empty and the current media come to an end. */ void slotFinished(); void slotAboutToFinish(); void slotNewTrackPlaying( const Phonon::MediaSource &source); void slotStateChanged( Phonon::State newState, Phonon::State oldState); - void slotPlayableUrlFetched( const KUrl &url ); + void slotPlayableUrlFetched( const QUrl &url ); void slotTick( qint64 ); void slotTrackLengthChanged( qint64 ); void slotMetaDataChanged(); void slotSeekableChanged( bool ); void slotPause(); /** * For volume/mute changes from the phonon side */ void slotVolumeChanged( qreal ); void slotMutedChanged( bool ); /** * Notify the engine that a new title has been reached when playing a cd. This * is needed as a cd counts as basically one lone track, and we want to be able * to play something else once one track has finished */ void slotTitleChanged( int titleNumber ); /** * Fill in m_supportedMimeTypes list and release m_supportedMimeTypesSemaphore. This * method must be called in the main thread so that there is no chance * Phonon::BackendCapabilities::availableMimeTypes() is called in a non-gui thread * for the first time. */ void slotFillInSupportedMimeTypes(); /** * Calls track->finishedPlaying(), connected to trackFinishedPlaying() signal to * reduce code duplication. */ void slotTrackFinishedPlaying( Meta::TrackPtr track, double playedFraction ); protected: // reimplemented from Meta::Observer using Observer::metadataChanged; virtual void metadataChanged( Meta::TrackPtr track ); virtual void metadataChanged( Meta::AlbumPtr album ); private: /** * Plays the media at a specified URL * * @param url the URL of the media * @param offset the position in the media to start at in milliseconds * @param startPaused if true, go to paused state. if false, go to playing state (default) */ - void playUrl( const KUrl &url, uint offset, bool startPaused = false ); + void playUrl( const QUrl &url, uint offset, bool startPaused = false ); /** * Try to detect MetaData spam in Streams etc. * * Some streams are doing advertisment in the metadata. We try to filter that * out. Additionally, some Phonon back-ends emit more than one * metadataChanged() signals per on track, so filter it all altogether. */ bool isInRecentMetaDataHistory( const QVariantMap &meta ); /** * If m_lastStreamStampPosition is non-negative, update it to current position * and update track length in current stream. */ void stampStreamTrackLength(); /** * emit metadataChanged() with info so that MetaStream::Track that is * currently listening updates its length. * * @param length new track length in milliseconds */ void updateStreamLength( qint64 length ); Q_DISABLE_COPY( EngineController ) EqualizerController *m_equalizerController; QWeakPointer m_media; QWeakPointer m_preamp; QWeakPointer m_audio; QWeakPointer m_audioDataOutput; QWeakPointer m_controller; Phonon::Path m_path; Phonon::Path m_dataPath; QWeakPointer m_fadeouter; QWeakPointer m_fader; Meta::TrackPtr m_currentTrack; Meta::AlbumPtr m_currentAlbum; Meta::TrackPtr m_nextTrack; - KUrl m_nextUrl; + QUrl m_nextUrl; Capabilities::BoundedPlaybackCapability* m_boundedPlayback; Capabilities::MultiPlayableCapability* m_multiPlayback; QScopedPointer m_multiSource; bool m_playWhenFetched; int m_volume; int m_currentAudioCdTrack; QTimer *m_pauseTimer; QList m_metaDataHistory; // against metadata spam // last position (in ms) when the song changed (within the current stream) or -1 for non-stream qint64 m_lastStreamStampPosition; /** * Some flags to prevent feedback loops in volume updates */ bool m_ignoreVolumeChangeAction; bool m_ignoreVolumeChangeObserve; // Used to get a more accurate estimate of the position for slotTick int m_tickInterval; qint64 m_lastTickPosition; qint64 m_lastTickCount; QMutex m_mutex; // FIXME: this variable should be updated when // Phonon::BackendCapabilities::notifier()'s capabilitiesChanged signal is emitted QStringList m_supportedMimeTypes; QSemaphore m_supportedMimeTypesSemaphore; }; namespace The { AMAROK_EXPORT EngineController* engineController(); } #endif diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 80914f21b0..eda33cf21c 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -1,1398 +1,1398 @@ /**************************************************************************************** * Copyright (c) 2002-2013 Mark Kretschmann * * Copyright (c) 2002 Max Howell * * Copyright (c) 2002 Gabor Lehel * * Copyright (c) 2002 Nikolaj Hald Nielsen * * Copyright (c) 2009 Artur Szymiec * * Copyright (c) 2010 Téo Mrnjavac * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "MainWindow" #include "MainWindow.h" #include "ActionClasses.h" #include "EngineController.h" //for actions in ctor #include "KNotificationBackend.h" #include "PaletteHandler.h" #include "PluginManager.h" #include "SvgHandler.h" #include "amarokconfig.h" #include "aboutdialog/ExtendedAboutDialog.h" #include "aboutdialog/OcsData.h" #include "amarokurls/AmarokUrlHandler.h" #include "amarokurls/BookmarkManager.h" #include "browsers/collectionbrowser/CollectionWidget.h" #include "browsers/filebrowser/FileBrowser.h" #include "browsers/playlistbrowser/PlaylistBrowser.h" #include "browsers/playlistbrowser/PodcastCategory.h" #include "browsers/servicebrowser/ServiceBrowser.h" #include "context/ContextDock.h" #include "core/meta/Statistics.h" #include "core/support/Amarok.h" #include "core/support/Components.h" #include "core/support/Debug.h" #include "core-impl/collections/support/CollectionManager.h" #include "covermanager/CoverManager.h" // for actions #include "dialogs/DiagnosticDialog.h" #include "dialogs/EqualizerDialog.h" #include "moodbar/MoodbarManager.h" #include "network/NetworkAccessManagerProxy.h" #ifdef DEBUG_BUILD_TYPE #include "network/NetworkAccessViewer.h" #endif // DEBUG_BUILD_TYPE #include "playlist/PlaylistActions.h" #include "playlist/PlaylistController.h" #include "playlist/PlaylistModelStack.h" #include "playlist/PlaylistDock.h" #include "playlist/ProgressiveSearchWidget.h" #include "playlist/layouts/LayoutConfigAction.h" #include "playlistmanager/PlaylistManager.h" #include "playlistmanager/file/PlaylistFileProvider.h" #include "services/scriptable/ScriptableService.h" #include "statsyncing/Controller.h" #include "toolbar/MainToolbar.h" #include "toolbar/SlimToolbar.h" #include "widgets/Osd.h" #include //m_actionCollection #include #include //kapp #include //openPlaylist() #include //slotAddStream() #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_WS_X11 #include #endif #ifdef Q_WS_MAC #include "mac/GrowlInterface.h" #ifdef HAVE_NOTIFICATION_CENTER #include "mac/MacSystemNotify.h" #endif #endif #define AMAROK_CAPTION I18N_NOOP( "Amarok" ) extern OcsData ocsData; QWeakPointer MainWindow::s_instance; namespace The { MainWindow* mainWindow() { return MainWindow::s_instance.data(); } } MainWindow::MainWindow() : KMainWindow( 0 ) , m_showMenuBar( 0 ) , m_lastBrowser( 0 ) , m_waitingForCd( false ) { DEBUG_BLOCK setObjectName( "MainWindow" ); s_instance = this; #ifdef Q_WS_MAC (void)new GrowlInterface( qApp->applicationName() ); #ifdef HAVE_NOTIFICATION_CENTER (void)new OSXNotify( qApp->applicationName() ); #endif #endif PERF_LOG( "Instantiate Collection Manager" ) CollectionManager::instance(); PERF_LOG( "Started Collection Manager instance" ) /* The PluginManager needs to be loaded before the playlist model * (which gets started by "statusBar::connectPlaylist" below so that it can handle any * tracks in the saved playlist that are associated with services. Eg, if * the playlist has a Magnatune track in it when Amarok is closed, then the * Magnatune service needs to be initialized before the playlist is loaded * here. */ PERF_LOG( "Instantiate Plugin Manager" ) The::pluginManager(); PERF_LOG( "Started Plugin Manager instance" ) createActions(); PERF_LOG( "Created actions" ) The::paletteHandler()->setPalette( palette() ); setPlainCaption( i18n( AMAROK_CAPTION ) ); init(); // We could as well move the code from init() here, but meh.. getting a tad long //restore active category ( as well as filters and levels and whatnot.. ) const QString path = Amarok::config().readEntry( "Browser Path", QString() ); if( !path.isEmpty() ) m_browserDock.data()->list()->navigate( path ); setAutoSaveSettings(); m_showMenuBar->setChecked(!menuBar()->isHidden()); // workaround for bug #171080 EngineController *engine = The::engineController(); connect( engine, SIGNAL(stopped(qint64,qint64)), this, SLOT(slotStopped()) ); connect( engine, SIGNAL(paused()), this, SLOT(slotPaused()) ); connect( engine, SIGNAL(trackPlaying(Meta::TrackPtr)), this, SLOT(slotNewTrackPlaying()) ); connect( engine, SIGNAL(trackMetadataChanged(Meta::TrackPtr)), this, SLOT(slotMetadataChanged(Meta::TrackPtr)) ); KGlobal::locale()->insertCatalog( "libplasma" ); } MainWindow::~MainWindow() { DEBUG_BLOCK //save currently active category Amarok::config().writeEntry( "Browser Path", m_browserDock.data()->list()->path() ); #ifdef DEBUG_BUILD_TYPE delete m_networkViewer.data(); #endif // DEBUG_BUILD_TYPE delete The::svgHandler(); delete The::paletteHandler(); } ///////// public interface /** * This function will initialize the main window. */ void MainWindow::init() { layout()->setContentsMargins( 0, 0, 0, 0 ); layout()->setSpacing( 0 ); //create main toolbar m_mainToolbar = new MainToolbar( 0 ); m_mainToolbar.data()->setAllowedAreas( Qt::TopToolBarArea | Qt::BottomToolBarArea ); m_mainToolbar.data()->setMovable ( true ); addToolBar( Qt::TopToolBarArea, m_mainToolbar.data() ); //create slim toolbar m_slimToolbar = new SlimToolbar( 0 ); m_slimToolbar.data()->setAllowedAreas( Qt::TopToolBarArea | Qt::BottomToolBarArea ); m_slimToolbar.data()->setMovable ( true ); addToolBar( Qt::TopToolBarArea, m_slimToolbar.data() ); m_slimToolbar.data()->hide(); //BEGIN Creating Widgets PERF_LOG( "Create sidebar" ) m_browserDock = new BrowserDock( this ); m_browserDock.data()->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Ignored ); m_browserDock.data()->installEventFilter( this ); PERF_LOG( "Sidebar created" ) PERF_LOG( "Create Playlist" ) m_playlistDock = new Playlist::Dock( this ); m_playlistDock.data()->installEventFilter( this ); //HACK, need to connect after because of order in MainWindow() connect( Amarok::actionCollection()->action( "playlist_edit_queue" ), SIGNAL(triggered(bool)), m_playlistDock.data(), SLOT(slotEditQueue()) ); PERF_LOG( "Playlist created" ) PERF_LOG( "Creating ContextWidget" ) m_contextDock = new ContextDock( this ); m_contextDock.data()->installEventFilter( this ); PERF_LOG( "ContextScene created" ) //END Creating Widgets createMenus(); PERF_LOG( "Loading default contextScene" ) PERF_LOG( "Loaded default contextScene" ) setDockOptions( QMainWindow::AllowNestedDocks | QMainWindow::AllowTabbedDocks | QMainWindow::AnimatedDocks | QMainWindow::VerticalTabs ); addDockWidget( Qt::LeftDockWidgetArea, m_browserDock.data() ); addDockWidget( Qt::LeftDockWidgetArea, m_contextDock.data(), Qt::Horizontal ); addDockWidget( Qt::LeftDockWidgetArea, m_playlistDock.data(), Qt::Horizontal ); setLayoutLocked( AmarokConfig::lockLayout() ); // { Debug::Block block( "Creating browsers. Please report long start times!" ); //TODO: parent these browsers? PERF_LOG( "Creating CollectionWidget" ) m_collectionBrowser = new CollectionWidget( "collections", 0 ); //TODO: rename "Music Collections m_collectionBrowser->setPrettyName( i18n( "Local Music" ) ); m_collectionBrowser->setIcon( KIcon( "drive-harddisk" ) ); m_collectionBrowser->setShortDescription( i18n( "Local sources of content" ) ); m_browserDock.data()->list()->addCategory( m_collectionBrowser ); PERF_LOG( "Created CollectionWidget" ) PERF_LOG( "Creating ServiceBrowser" ) ServiceBrowser *serviceBrowser = ServiceBrowser::instance(); serviceBrowser->setParent( 0 ); serviceBrowser->setPrettyName( i18n( "Internet" ) ); serviceBrowser->setIcon( KIcon( "applications-internet" ) ); serviceBrowser->setShortDescription( i18n( "Online sources of content" ) ); m_browserDock.data()->list()->addCategory( serviceBrowser ); PERF_LOG( "Created ServiceBrowser" ) PERF_LOG( "Creating PlaylistBrowser" ) m_playlistBrowser = new PlaylistBrowserNS::PlaylistBrowser( "playlists", 0 ); m_playlistBrowser->setPrettyName( i18n("Playlists") ); m_playlistBrowser->setIcon( KIcon( "view-media-playlist-amarok" ) ); m_playlistBrowser->setShortDescription( i18n( "Various types of playlists" ) ); m_browserDock.data()->list()->addCategory( m_playlistBrowser ); PERF_LOG( "CreatedPlaylsitBrowser" ) PERF_LOG( "Creating FileBrowser" ) FileBrowser *fileBrowser = new FileBrowser( "files", 0 ); fileBrowser->setPrettyName( i18n("Files") ); fileBrowser->setIcon( KIcon( "folder-amarok" ) ); fileBrowser->setShortDescription( i18n( "Browse local hard drive for content" ) ); m_browserDock.data()->list()->addCategory( fileBrowser ); PERF_LOG( "Created FileBrowser" ) serviceBrowser->setScriptableServiceManager( The::scriptableServiceManager() ); PERF_LOG( "ScriptableServiceManager done" ) PERF_LOG( "Creating Podcast Category" ) m_browserDock.data()->list()->addCategory( The::podcastCategory() ); PERF_LOG( "Created Podcast Category" ) // If Amarok is started for the first time, set initial dock widget sizes if( !Amarok::config( "MainWindow" ).hasKey( "State" ) ) QTimer::singleShot( 0, this, SLOT(setDefaultDockSizes()) ); PERF_LOG( "finished MainWindow::init" ) } The::amarokUrlHandler(); //Instantiate The::coverFetcher(); //Instantiate // we must filter ourself to get mouseevents on the "splitter" - what is us, but filtered by the layouter installEventFilter( this ); } QMenu* MainWindow::createPopupMenu() { QMenu* menu = new QMenu( this ); // Show/hide menu bar if (!menuBar()->isVisible()) menu->addAction( m_showMenuBar ); menu->addSeparator(); addViewMenuItems(menu); return menu; } void MainWindow::addViewMenuItems(QMenu* menu) { menu->setTitle( i18nc("@item:inmenu", "&View" ) ); // Layout locking: QAction* lockAction = new QAction( i18n( "Lock Layout" ), this ); lockAction->setCheckable( true ); lockAction->setChecked( AmarokConfig::lockLayout() ); connect( lockAction, SIGNAL(toggled(bool)), SLOT(setLayoutLocked(bool)) ); menu->addAction( lockAction ); menu->addSeparator(); // Dock widgets: QList dockwidgets = qFindChildren( this ); foreach( QDockWidget* dockWidget, dockwidgets ) { if( dockWidget->parentWidget() == this ) menu->addAction( dockWidget->toggleViewAction() ); } menu->addSeparator(); // Toolbars: QList toolbars = qFindChildren( this ); QActionGroup* toolBarGroup = new QActionGroup( this ); toolBarGroup->setExclusive( true ); foreach( QToolBar* toolBar, toolbars ) { if( toolBar->parentWidget() == this ) { QAction* action = toolBar->toggleViewAction(); connect( action, SIGNAL(toggled(bool)), toolBar, SLOT(setVisible(bool)) ); toolBarGroup->addAction( action ); menu->addAction( action ); } } menu->addSeparator(); QAction *resetAction = new QAction( i18n( "Reset Layout" ), this ); connect( resetAction, SIGNAL( triggered() ), this, SLOT( resetLayout() ) ); menu->addAction( resetAction ); } void MainWindow::showBrowser( const QString &name ) { Q_UNUSED( name ); // showBrowser( index ); // FIXME } void MainWindow::showDock( AmarokDockId dockId ) { QString name; switch( dockId ) { case AmarokDockNavigation: name = m_browserDock.data()->windowTitle(); break; case AmarokDockContext: name = m_contextDock.data()->windowTitle(); break; case AmarokDockPlaylist: name = m_playlistDock.data()->windowTitle(); break; } QList < QTabBar * > tabList = findChildren < QTabBar * > (); foreach( QTabBar *bar, tabList ) { for( int i = 0; i < bar->count(); i++ ) { if( bar->tabText( i ) == name ) { bar->setCurrentIndex( i ); break; } } } } void MainWindow::closeEvent( QCloseEvent *e ) { #ifdef Q_WS_MAC Q_UNUSED( e ); hide(); #else //KDE policy states we should hide to tray and not quit() when the //close window button is pushed for the main widget if( AmarokConfig::showTrayIcon() && e->spontaneous() && !kapp->sessionSaving() ) { KMessageBox::information( this, i18n( "Closing the main window will keep Amarok running in the System Tray. " "Use Quit from the menu, or the Amarok tray icon to exit the application." ), i18n( "Docking in System Tray" ), "hideOnCloseInfo" ); hide(); e->ignore(); return; } e->accept(); App::instance()->quit(); #endif } void MainWindow::exportPlaylist() //SLOT { DEBUG_BLOCK - QScopedPointer fileDialog( new KFileDialog( KUrl("kfiledialog:///amarok-playlist-export"), QString(), this ) ); + QScopedPointer fileDialog( new KFileDialog( QUrl("kfiledialog:///amarok-playlist-export"), QString(), this ) ); QCheckBox *saveRelativeCheck = new QCheckBox( i18n("Use relative path for &saving") ); saveRelativeCheck->setChecked( AmarokConfig::relativePlaylist() ); QStringList supportedMimeTypes; supportedMimeTypes << "video/x-ms-asf"; //ASX supportedMimeTypes << "audio/x-mpegurl"; //M3U supportedMimeTypes << "audio/x-scpls"; //PLS supportedMimeTypes << "application/xspf+xml"; //XSPF fileDialog->setMimeFilter( supportedMimeTypes, supportedMimeTypes.first() ); fileDialog->fileWidget()->setCustomWidget( saveRelativeCheck ); fileDialog->setOperationMode( KFileDialog::Saving ); fileDialog->setMode( KFile::File ); fileDialog->setCaption( i18n("Save As") ); fileDialog->setObjectName( "PlaylistExport" ); fileDialog->exec(); QString playlistPath = fileDialog->selectedFile(); if( !playlistPath.isEmpty() ) The::playlist()->exportPlaylist( playlistPath, saveRelativeCheck->isChecked() ); } void MainWindow::slotShowActiveTrack() const { m_playlistDock.data()->showActiveTrack(); } void MainWindow::slotEditTrackInfo() const { m_playlistDock.data()->editTrackInfo(); } void MainWindow::slotShowCoverManager() //SLOT { CoverManager::showOnce( QString(), this ); } void MainWindow::slotShowDiagnosticsDialog() { DiagnosticDialog *dialog = new DiagnosticDialog( KGlobal::mainComponent().aboutData(), this ); dialog->show(); } void MainWindow::slotShowBookmarkManager() { BookmarkManager::showOnce( this ); } void MainWindow::slotShowEqualizer() { EqualizerDialog::showOnce( this ); } void MainWindow::slotPlayMedia() //SLOT { // Request location and immediately start playback slotAddLocation( true ); } void MainWindow::slotAddLocation( bool directPlay ) //SLOT { - static KUrl lastDirectory; + static QUrl lastDirectory; // open a file selector to add media to the playlist - KUrl::List files; - KFileDialog dlg( KUrl(QDesktopServices::storageLocation(QDesktopServices::MusicLocation) ), QString("*.*|"), this ); + QList files; + KFileDialog dlg( QUrl(QDesktopServices::storageLocation(QDesktopServices::MusicLocation) ), QString("*.*|"), this ); if( !lastDirectory.isEmpty() ) dlg.setUrl( lastDirectory ); dlg.setCaption( directPlay ? i18n("Play Media (Files or URLs)") : i18n("Add Media (Files or URLs)") ); dlg.setMode( KFile::Files | KFile::Directory ); dlg.setObjectName( "PlayMedia" ); dlg.exec(); files = dlg.selectedUrls(); lastDirectory = dlg.baseUrl(); if( files.isEmpty() ) return; Playlist::AddOptions options = directPlay ? Playlist::OnPlayMediaAction : Playlist::OnAppendToPlaylistAction; The::playlistController()->insertOptioned( files, options ); } void MainWindow::slotAddStream() //SLOT { bool ok; QString url = KInputDialog::getText( i18n( "Add Stream" ), i18n( "Enter Stream URL:" ), QString(), &ok, this ); if( !ok ) return; - The::playlistController()->insertOptioned( KUrl( url ), + The::playlistController()->insertOptioned( QUrl( url ), Playlist::OnAppendToPlaylistAction | Playlist::RemotePlaylistsAreStreams ); } void MainWindow::slotFocusPlaylistSearch() { showDock( AmarokDockPlaylist ); // ensure that the dock is visible if tabbed m_playlistDock.data()->searchWidget()->focusInputLine(); } void MainWindow::slotFocusCollectionSearch() { // ensure collection browser is activated within navigation dock: browserDock()->list()->navigate( QString("collections") ); showDock( AmarokDockNavigation ); // ensure that the dock is visible if tabbed m_collectionBrowser->focusInputLine(); } #ifdef DEBUG_BUILD_TYPE void MainWindow::showNetworkRequestViewer() //SLOT { if( !m_networkViewer ) { m_networkViewer = new NetworkAccessViewer( this ); The::networkAccessManager()->setNetworkAccessViewer( m_networkViewer.data() ); } The::networkAccessManager()->networkAccessViewer()->show(); } #endif // DEBUG_BUILD_TYPE /** * "Toggle Main Window" global shortcut connects to this slot */ void MainWindow::showHide() //SLOT { const KWindowInfo info = KWindowSystem::windowInfo( winId(), 0, 0 ); const int currentDesktop = KWindowSystem::currentDesktop(); if( !isVisible() ) { setVisible( true ); } else { if( !isMinimized() ) { if( !isActiveWindow() ) // not minimised and without focus { KWindowSystem::setOnDesktop( winId(), currentDesktop ); KWindowSystem::activateWindow( winId() ); } else // Amarok has focus { setVisible( false ); } } else // Amarok is minimised { setWindowState( windowState() & ~Qt::WindowMinimized ); KWindowSystem::setOnDesktop( winId(), currentDesktop ); KWindowSystem::activateWindow( winId() ); } } } void MainWindow::showNotificationPopup() // slot { if( Amarok::KNotificationBackend::instance()->isEnabled() && !Amarok::OSD::instance()->isEnabled() ) Amarok::KNotificationBackend::instance()->showCurrentTrack(); else Amarok::OSD::instance()->forceToggleOSD(); } void MainWindow::slotFullScreen() // slot { setWindowState( windowState() ^ Qt::WindowFullScreen ); } void MainWindow::slotLoveTrack() { emit loveTrack( The::engineController()->currentTrack() ); } void MainWindow::slotBanTrack() { emit banTrack( The::engineController()->currentTrack() ); } void MainWindow::slotShufflePlaylist() { m_playlistDock.data()->sortWidget()->trimToLevel(); The::playlistActions()->shuffle(); } void MainWindow::slotSeekForwardShort() { EngineController* ec = The::engineController(); ec->seekBy( AmarokConfig::seekShort() * 1000 ); } void MainWindow::slotSeekForwardMedium() { EngineController* ec = The::engineController(); ec->seekBy( AmarokConfig::seekMedium() * 1000 ); } void MainWindow::slotSeekForwardLong() { EngineController* ec = The::engineController(); ec->seekBy( AmarokConfig::seekLong() * 1000 ); } void MainWindow::slotSeekBackwardShort() { EngineController* ec = The::engineController(); ec->seekBy( AmarokConfig::seekShort() * -1000 ); } void MainWindow::slotSeekBackwardMedium() { EngineController* ec = The::engineController(); ec->seekBy( AmarokConfig::seekMedium() * -1000 ); } void MainWindow::slotSeekBackwardLong() { EngineController* ec = The::engineController(); ec->seekBy( AmarokConfig::seekLong() * -1000 ); } void MainWindow::slotPutCurrentTrackToClipboard() { Meta::TrackPtr currentTrack = The::engineController()->currentTrack(); if ( currentTrack ) { QString text; Meta::ArtistPtr artist = currentTrack->artist(); if( artist ) text = artist->prettyName() + " - "; text += currentTrack->prettyName(); QClipboard *clipboard = QApplication::clipboard(); clipboard->setText( text ); } } void MainWindow::activate() { #ifdef Q_WS_X11 const KWindowInfo info = KWindowSystem::windowInfo( winId(), 0, 0 ); if( KWindowSystem::activeWindow() != winId() ) setVisible( true ); else if( !info.isMinimized() ) setVisible( true ); if( !isHidden() ) KWindowSystem::activateWindow( winId() ); #else setVisible( true ); #endif } void MainWindow::createActions() { KActionCollection* const ac = Amarok::actionCollection(); const EngineController* const ec = The::engineController(); const Playlist::Actions* const pa = The::playlistActions(); const Playlist::Controller* const pc = The::playlistController(); KStandardAction::keyBindings( kapp, SLOT(slotConfigShortcuts()), ac ); m_showMenuBar = KStandardAction::showMenubar(this, SLOT(slotShowMenuBar()), ac); KStandardAction::preferences( kapp, SLOT(slotConfigAmarok()), ac ); ac->action( KStandardAction::name( KStandardAction::KeyBindings ) )->setIcon( KIcon( "configure-shortcuts-amarok" ) ); ac->action( KStandardAction::name( KStandardAction::Preferences ) )->setIcon( KIcon( "configure-amarok" ) ); ac->action( KStandardAction::name( KStandardAction::Preferences ) )->setMenuRole(QAction::PreferencesRole); // Define OS X Prefs menu here, removes need for ifdef later KStandardAction::quit( App::instance(), SLOT(quit()), ac ); KAction *action = new KAction( KIcon( "document-open" ), i18n("&Add Media..."), this ); ac->addAction( "playlist_add", action ); connect( action, SIGNAL(triggered(bool)), this, SLOT(slotAddLocation()) ); action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_A ) ); action = new KAction( KIcon( "edit-clear-list" ), i18nc( "clear playlist", "&Clear Playlist" ), this ); connect( action, SIGNAL(triggered(bool)), pc, SLOT(clear()) ); ac->addAction( "playlist_clear", action ); action = new KAction( KIcon( "format-list-ordered" ), i18nc( "edit play queue of playlist", "Edit &Queue" ), this ); //Qt::META+Qt::Key_Q is taken by Plasma as a global action->setShortcut( KShortcut( Qt::META + Qt::Key_U ) ); ac->addAction( "playlist_edit_queue", action );; action = new KAction( i18nc( "Remove duplicate and dead (unplayable) tracks from the playlist", "Re&move Duplicates" ), this ); connect( action, SIGNAL(triggered(bool)), pc, SLOT(removeDeadAndDuplicates()) ); ac->addAction( "playlist_remove_dead_and_duplicates", action ); action = new Playlist::LayoutConfigAction( this ); ac->addAction( "playlist_layout", action ); action = new KAction( KIcon( "document-open-remote" ), i18n("&Add Stream..."), this ); connect( action, SIGNAL(triggered(bool)), this, SLOT(slotAddStream()) ); ac->addAction( "stream_add", action ); action = new KAction( KIcon( "document-export-amarok" ), i18n("&Export Playlist As..."), this ); connect( action, SIGNAL(triggered(bool)), this, SLOT(exportPlaylist()) ); ac->addAction( "playlist_export", action ); action = new KAction( KIcon( "bookmark-new" ), i18n( "Bookmark Media Sources View" ), this ); ac->addAction( "bookmark_browser", action ); connect( action, SIGNAL(triggered()), The::amarokUrlHandler(), SLOT(bookmarkCurrentBrowserView()) ); action = new KAction( KIcon( "bookmarks-organize" ), i18n( "Bookmark Manager" ), this ); ac->addAction( "bookmark_manager", action ); connect( action, SIGNAL(triggered(bool)), SLOT(slotShowBookmarkManager()) ); action = new KAction( KIcon( "view-media-equalizer" ), i18n( "Equalizer" ), this ); ac->addAction( "equalizer_dialog", action ); connect( action, SIGNAL(triggered(bool)), SLOT(slotShowEqualizer()) ); action = new KAction( KIcon( "bookmark-new" ), i18n( "Bookmark Playlist Setup" ), this ); ac->addAction( "bookmark_playlistview", action ); connect( action, SIGNAL(triggered()), The::amarokUrlHandler(), SLOT(bookmarkCurrentPlaylistView()) ); action = new KAction( KIcon( "bookmark-new" ), i18n( "Bookmark Context Applets" ), this ); ac->addAction( "bookmark_contextview", action ); connect( action, SIGNAL(triggered()), The::amarokUrlHandler(), SLOT(bookmarkCurrentContextView()) ); action = new KAction( KIcon( "media-album-cover-manager-amarok" ), i18n( "Cover Manager" ), this ); connect( action, SIGNAL(triggered(bool)), SLOT(slotShowCoverManager()) ); ac->addAction( "cover_manager", action ); action = new KAction( KIcon("document-open"), i18n("Play Media..."), this ); ac->addAction( "playlist_playmedia", action ); action->setShortcut( Qt::CTRL + Qt::Key_O ); connect( action, SIGNAL(triggered(bool)), SLOT(slotPlayMedia()) ); action = new KAction( KIcon("media-track-edit-amarok"), i18n("Edit Details of Currently Selected Track"), this ); ac->addAction( "trackdetails_edit", action ); action->setShortcut( Qt::CTRL + Qt::Key_E ); connect( action, SIGNAL(triggered(bool)), SLOT(slotEditTrackInfo()) ); action = new KAction( KIcon( "media-seek-forward-amarok" ), i18n( "Seek Forward by %1", KGlobal::locale()->prettyFormatDuration( AmarokConfig::seekShort() * 1000 ) ), this ); ac->addAction( "seek_forward_short", action ); action->setShortcut( Qt::CTRL + Qt::Key_Right ); connect( action, SIGNAL(triggered(bool)), SLOT(slotSeekForwardShort()) ); action = new KAction( KIcon( "media-seek-forward-amarok" ), i18n( "Seek Forward by %1", KGlobal::locale()->prettyFormatDuration( AmarokConfig::seekMedium() * 1000 ) ), this ); ac->addAction( "seek_forward_medium", action ); action->setShortcut( Qt::Key_Right ); action->setGlobalShortcut( KShortcut( Qt::META + Qt::SHIFT + Qt::Key_Plus ) ); connect( action, SIGNAL(triggered(bool)), SLOT(slotSeekForwardMedium()) ); action = new KAction( KIcon( "media-seek-forward-amarok" ), i18n( "Seek Forward by %1", KGlobal::locale()->prettyFormatDuration( AmarokConfig::seekLong() * 1000 ) ), this ); ac->addAction( "seek_forward_long", action ); action->setShortcut( Qt::SHIFT + Qt::Key_Right ); connect( action, SIGNAL(triggered(bool)), SLOT(slotSeekForwardLong()) ); action = new KAction( KIcon( "media-seek-backward-amarok" ), i18n( "Seek Backward by %1", KGlobal::locale()->prettyFormatDuration( AmarokConfig::seekShort() * 1000 ) ), this ); ac->addAction( "seek_backward_short", action ); action->setShortcut( Qt::CTRL + Qt::Key_Left ); connect( action, SIGNAL(triggered(bool)), SLOT(slotSeekBackwardShort()) ); action = new KAction( KIcon( "media-seek-backward-amarok" ), i18n( "Seek Backward by %1", KGlobal::locale()->prettyFormatDuration( AmarokConfig::seekMedium() * 1000 ) ), this ); ac->addAction( "seek_backward_medium", action ); action->setShortcut( Qt::Key_Left ); action->setGlobalShortcut( KShortcut( Qt::META + Qt::SHIFT + Qt::Key_Minus ) ); connect( action, SIGNAL(triggered(bool)), SLOT(slotSeekBackwardMedium()) ); action = new KAction( KIcon( "media-seek-backward-amarok" ), i18n( "Seek Backward by %1", KGlobal::locale()->prettyFormatDuration( AmarokConfig::seekLong() * 1000 ) ), this ); ac->addAction( "seek_backward_long", action ); action->setShortcut( Qt::SHIFT + Qt::Key_Left ); connect( action, SIGNAL(triggered(bool)), SLOT(slotSeekBackwardLong()) ); PERF_LOG( "MainWindow::createActions 6" ) action = new KAction( KIcon("view-refresh"), i18n( "Update Collection" ), this ); connect ( action, SIGNAL(triggered(bool)), CollectionManager::instance(), SLOT(checkCollectionChanges()) ); ac->addAction( "update_collection", action ); action = new KAction( KIcon( "amarok_playcount" ), i18n( "Synchronize Statistics..." ), this ); ac->addAction( "synchronize_statistics", action ); connect( action, SIGNAL(triggered(bool)), Amarok::Components::statSyncingController(), SLOT(synchronize()) ); action = new KAction( this ); ac->addAction( "prev", action ); action->setIcon( KIcon("media-skip-backward-amarok") ); action->setText( i18n( "Previous Track" ) ); action->setGlobalShortcut( KShortcut() ); connect( action, SIGNAL(triggered(bool)), pa, SLOT(back()) ); action = new KAction( this ); ac->addAction( "replay", action ); action->setIcon( KIcon("media-playback-start") ); action->setText( i18n( "Restart current track" ) ); action->setGlobalShortcut( KShortcut() ); connect( action, SIGNAL(triggered(bool)), ec, SLOT(replay()) ); action = new KAction( this ); ac->addAction( "shuffle_playlist", action ); action->setIcon( KIcon("media-playlist-shuffle") ); action->setText( i18n( "Shuffle Playlist" ) ); action->setShortcut( Qt::CTRL + Qt::Key_H ); connect( action, SIGNAL(triggered(bool)), this, SLOT(slotShufflePlaylist()) ); action = new KAction( this ); ac->addAction( "repopulate", action ); action->setText( i18n( "Repopulate Playlist" ) ); action->setIcon( KIcon("view-refresh-amarok") ); connect( action, SIGNAL(triggered(bool)), pa, SLOT(repopulateDynamicPlaylist()) ); action = new KAction( this ); ac->addAction( "disable_dynamic", action ); action->setText( i18n( "Disable Dynamic Playlist" ) ); action->setIcon( KIcon("edit-delete-amarok") ); //this is connected inside the dynamic playlist category action = new KAction( KIcon("media-skip-forward-amarok"), i18n( "Next Track" ), this ); ac->addAction( "next", action ); action->setGlobalShortcut( KShortcut() ); connect( action, SIGNAL(triggered(bool)), pa, SLOT(next()) ); action = new KAction( i18n( "Increase Volume" ), this ); ac->addAction( "increaseVolume", action ); action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_Plus ) ); action->setShortcut( Qt::Key_Plus ); connect( action, SIGNAL(triggered()), ec, SLOT(increaseVolume()) ); action = new KAction( i18n( "Decrease Volume" ), this ); ac->addAction( "decreaseVolume", action ); action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_Minus ) ); action->setShortcut( Qt::Key_Minus ); connect( action, SIGNAL(triggered()), ec, SLOT(decreaseVolume()) ); action = new KAction( i18n( "Toggle Main Window" ), this ); ac->addAction( "toggleMainWindow", action ); action->setGlobalShortcut( KShortcut() ); connect( action, SIGNAL(triggered()), SLOT(showHide()) ); action = new KAction( i18n( "Toggle Full Screen" ), this ); ac->addAction( "toggleFullScreen", action ); action->setShortcut( KShortcut( Qt::CTRL + Qt::SHIFT + Qt::Key_F ) ); connect( action, SIGNAL(triggered()), SLOT(slotFullScreen()) ); action = new KAction( i18n( "Search playlist" ), this ); ac->addAction( "searchPlaylist", action ); action->setShortcut( KShortcut( Qt::CTRL + Qt::Key_J ) ); connect( action, SIGNAL(triggered()), SLOT(slotFocusPlaylistSearch()) ); action = new KAction( i18n( "Search collection" ), this ); ac->addAction( "searchCollection", action ); action->setShortcut( KShortcut( Qt::CTRL + Qt::Key_F ) ); connect( action, SIGNAL(triggered()), SLOT(slotFocusCollectionSearch()) ); action = new KAction( KIcon( "music-amarok" ), i18n("Show active track"), this ); ac->addAction( "show_active_track", action ); connect( action, SIGNAL(triggered(bool)), SLOT(slotShowActiveTrack()) ); action = new KAction( i18n( "Show Notification Popup" ), this ); ac->addAction( "showNotificationPopup", action ); action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_O ) ); connect( action, SIGNAL(triggered()), SLOT(showNotificationPopup()) ); action = new KAction( i18n( "Mute Volume" ), this ); ac->addAction( "mute", action ); action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_M ) ); connect( action, SIGNAL(triggered()), ec, SLOT(toggleMute()) ); action = new KAction( i18n( "Last.fm: Love Current Track" ), this ); ac->addAction( "loveTrack", action ); action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_L ) ); connect( action, SIGNAL(triggered()), SLOT(slotLoveTrack()) ); action = new KAction( i18n( "Last.fm: Ban Current Track" ), this ); ac->addAction( "banTrack", action ); //action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_B ) ); connect( action, SIGNAL(triggered()), SLOT(slotBanTrack()) ); action = new KAction( i18n( "Last.fm: Skip Current Track" ), this ); ac->addAction( "skipTrack", action ); action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_S ) ); connect( action, SIGNAL(triggered()), SIGNAL(skipTrack()) ); action = new KAction( KIcon( "media-track-queue-amarok" ), i18n( "Queue Track" ), this ); ac->addAction( "queueTrack", action ); action->setShortcut( KShortcut( Qt::CTRL + Qt::Key_D ) ); connect( action, SIGNAL(triggered()), SIGNAL(switchQueueStateShortcut()) ); action = new KAction( i18n( "Put Artist - Title of the current track to the clipboard" ), this ); ac->addAction("artistTitleClipboard", action); action->setShortcut( KShortcut( Qt::CTRL + Qt::Key_C ) ); connect( action, SIGNAL(triggered()), SLOT(slotPutCurrentTrackToClipboard()) ); action = new KAction( i18n( "Rate Current Track: 1" ), this ); ac->addAction( "rate1", action ); action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_1 ) ); connect( action, SIGNAL(triggered()), SLOT(setRating1()) ); action = new KAction( i18n( "Rate Current Track: 2" ), this ); ac->addAction( "rate2", action ); action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_2 ) ); connect( action, SIGNAL(triggered()), SLOT(setRating2()) ); action = new KAction( i18n( "Rate Current Track: 3" ), this ); ac->addAction( "rate3", action ); action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_3 ) ); connect( action, SIGNAL(triggered()), SLOT(setRating3()) ); action = new KAction( i18n( "Rate Current Track: 4" ), this ); ac->addAction( "rate4", action ); action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_4 ) ); connect( action, SIGNAL(triggered()), SLOT(setRating4()) ); action = new KAction( i18n( "Rate Current Track: 5" ), this ); ac->addAction( "rate5", action ); action->setGlobalShortcut( KShortcut( Qt::META + Qt::Key_5 ) ); connect( action, SIGNAL(triggered()), SLOT(setRating5()) ); #ifdef DEBUG_BUILD_TYPE action = new KAction( i18n( "Network Request Viewer" ), this ); ac->addAction( "network_request_viewer", action ); action->setIcon( KIcon( "utilities-system-monitor" ) ); connect( action, SIGNAL(triggered()), SLOT(showNetworkRequestViewer()) ); #endif // DEBUG_BUILD_TYPE action = KStandardAction::redo( pc, SLOT(redo()), this); ac->addAction( "playlist_redo", action ); action->setEnabled( false ); action->setIcon( KIcon( "edit-redo" ) ); connect( pc, SIGNAL(canRedoChanged(bool)), action, SLOT(setEnabled(bool)) ); action = KStandardAction::undo( pc, SLOT(undo()), this); ac->addAction( "playlist_undo", action ); action->setEnabled( false ); action->setIcon( KIcon( "edit-undo" ) ); connect( pc, SIGNAL(canUndoChanged(bool)), action, SLOT(setEnabled(bool)) ); action = new KAction( KIcon( "amarok" ), i18n( "&About Amarok" ), this ); ac->addAction( "extendedAbout", action ); connect( action, SIGNAL(triggered()), SLOT(showAbout()) ); action = new KAction ( KIcon( "info-amarok" ), i18n( "&Diagnostics" ), this ); ac->addAction( "diagnosticDialog", action ); connect( action, SIGNAL(triggered()), SLOT(slotShowDiagnosticsDialog()) ); action = new KAction( KIcon( "tools-report-bug" ), i18n("&Report Bug..."), this ); ac->addAction( "reportBug", action ); connect( action, SIGNAL(triggered()), SLOT(showReportBug()) ); PERF_LOG( "MainWindow::createActions 8" ) new Amarok::MenuAction( ac, this ); new Amarok::StopAction( ac, this ); new Amarok::StopPlayingAfterCurrentTrackAction( ac, this ); new Amarok::PlayPauseAction( ac, this ); new Amarok::ReplayGainModeAction( ac, this ); ac->addAssociatedWidget( this ); foreach( QAction* action, ac->actions() ) action->setShortcutContext( Qt::WindowShortcut ); } void MainWindow::setRating( int n ) { n *= 2; Meta::TrackPtr track = The::engineController()->currentTrack(); if( track ) { Meta::StatisticsPtr statistics = track->statistics(); // if we're setting an identical rating then we really must // want to set the half-star below rating if( statistics->rating() == n ) n -= 1; statistics->setRating( n ); Amarok::OSD::instance()->OSDWidget::ratingChanged( statistics->rating() ); } } void MainWindow::createMenus() { m_menubar = menuBar(); //BEGIN Actions menu KMenu *actionsMenu = new KMenu( m_menubar.data() ); #ifdef Q_WS_MAC // Add these functions to the dock icon menu in OS X //extern void qt_mac_set_dock_menu(QMenu *); //qt_mac_set_dock_menu(actionsMenu); // Change to avoid duplicate menu titles in OS X actionsMenu->setTitle( i18n("&Music") ); #else actionsMenu->setTitle( i18n("&Amarok") ); #endif actionsMenu->addAction( Amarok::actionCollection()->action("playlist_playmedia") ); actionsMenu->addSeparator(); actionsMenu->addAction( Amarok::actionCollection()->action("prev") ); actionsMenu->addAction( Amarok::actionCollection()->action("play_pause") ); actionsMenu->addAction( Amarok::actionCollection()->action("stop") ); actionsMenu->addAction( Amarok::actionCollection()->action("stop_after_current") ); actionsMenu->addAction( Amarok::actionCollection()->action("next") ); #ifndef Q_WS_MAC // Avoid duplicate "Quit" in OS X dock menu actionsMenu->addSeparator(); actionsMenu->addAction( Amarok::actionCollection()->action( KStandardAction::name( KStandardAction::Quit ) ) ); #endif //END Actions menu //BEGIN View menu QMenu* viewMenu = new QMenu(this); addViewMenuItems(viewMenu); //END View menu //BEGIN Playlist menu KMenu *playlistMenu = new KMenu( m_menubar.data() ); playlistMenu->setTitle( i18n("&Playlist") ); playlistMenu->addAction( Amarok::actionCollection()->action("playlist_add") ); playlistMenu->addAction( Amarok::actionCollection()->action("stream_add") ); //playlistMenu->addAction( Amarok::actionCollection()->action("playlist_save") ); //FIXME: See FIXME in PlaylistDock.cpp playlistMenu->addAction( Amarok::actionCollection()->action( "playlist_export" ) ); playlistMenu->addSeparator(); playlistMenu->addAction( Amarok::actionCollection()->action("playlist_undo") ); playlistMenu->addAction( Amarok::actionCollection()->action("playlist_redo") ); playlistMenu->addSeparator(); playlistMenu->addAction( Amarok::actionCollection()->action("playlist_clear") ); playlistMenu->addAction( Amarok::actionCollection()->action("playlist_remove_dead_and_duplicates") ); playlistMenu->addAction( Amarok::actionCollection()->action("playlist_layout") ); playlistMenu->addAction( Amarok::actionCollection()->action("playlist_edit_queue") ); //END Playlist menu //BEGIN Tools menu m_toolsMenu = new KMenu( m_menubar.data() ); m_toolsMenu.data()->setTitle( i18n("&Tools") ); m_toolsMenu.data()->addAction( Amarok::actionCollection()->action("bookmark_manager") ); m_toolsMenu.data()->addAction( Amarok::actionCollection()->action("cover_manager") ); m_toolsMenu.data()->addAction( Amarok::actionCollection()->action("equalizer_dialog") ); #ifdef DEBUG_BUILD_TYPE m_toolsMenu.data()->addAction( Amarok::actionCollection()->action("network_request_viewer") ); #endif // DEBUG_BUILD_TYPE m_toolsMenu.data()->addSeparator(); m_toolsMenu.data()->addAction( Amarok::actionCollection()->action("update_collection") ); m_toolsMenu.data()->addAction( Amarok::actionCollection()->action("synchronize_statistics") ); //END Tools menu //BEGIN Settings menu m_settingsMenu = new KMenu( m_menubar.data() ); m_settingsMenu.data()->setTitle( i18n("&Settings") ); m_settingsMenu.data()->addAction( Amarok::actionCollection()->action( KStandardAction::name( KStandardAction::ShowMenubar ) ) ); //TODO use KStandardAction or KXmlGuiWindow // the phonon-coreaudio backend has major issues with either the VolumeFaderEffect itself // or with it in the pipeline. track playback stops every ~3-4 tracks, and on tracks >5min it // stops at about 5:40. while we get this resolved upstream, don't make playing amarok such on osx. // so we disable replaygain on osx #ifndef Q_WS_MAC m_settingsMenu.data()->addAction( Amarok::actionCollection()->action("replay_gain_mode") ); m_settingsMenu.data()->addSeparator(); #endif m_settingsMenu.data()->addAction( Amarok::actionCollection()->action( KStandardAction::name( KStandardAction::KeyBindings ) ) ); m_settingsMenu.data()->addAction( Amarok::actionCollection()->action( KStandardAction::name( KStandardAction::Preferences ) ) ); //END Settings menu m_menubar.data()->addMenu( actionsMenu ); m_menubar.data()->addMenu( viewMenu ); m_menubar.data()->addMenu( playlistMenu ); m_menubar.data()->addMenu( m_toolsMenu.data() ); m_menubar.data()->addMenu( m_settingsMenu.data() ); KMenu *helpMenu = Amarok::Menu::helpMenu(); helpMenu->insertAction( helpMenu->actions().last(), Amarok::actionCollection()->action( "extendedAbout" ) ); helpMenu->insertAction( helpMenu->actions().last(), Amarok::actionCollection()->action( "diagnosticDialog" ) ); m_menubar.data()->addSeparator(); m_menubar.data()->addMenu( helpMenu ); } void MainWindow::slotShowMenuBar() { if (!m_showMenuBar->isChecked()) { //User have chosen to hide a menu. Lets warn him if (KMessageBox::warningContinueCancel(this, i18n("You have chosen to hide the menu bar.\n\nPlease remember that you can always use the shortcut \"%1\" to bring it back.", m_showMenuBar->shortcut().toString() ), i18n("Hide Menu"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), "showMenubar") != KMessageBox::Continue) { //Cancel menu hiding. Revert menu item to checked state. m_showMenuBar->setChecked(true); return; } } menuBar()->setVisible(m_showMenuBar->isChecked()); } void MainWindow::showAbout() { ExtendedAboutDialog dialog( KGlobal::mainComponent().aboutData(), &ocsData ); dialog.exec(); } void MainWindow::showReportBug() { KBugReport * rbDialog = new KBugReport( this, true, KGlobal::mainComponent().aboutData() ); rbDialog->setObjectName( "KBugReport" ); rbDialog->exec(); } void MainWindow::changeEvent( QEvent *event ) { if( event->type() == QEvent::PaletteChange ) The::paletteHandler()->setPalette( palette() ); } void MainWindow::slotStopped() { setPlainCaption( i18n( AMAROK_CAPTION ) ); } void MainWindow::slotPaused() { setPlainCaption( i18n( "Paused :: %1", i18n( AMAROK_CAPTION ) ) ); } void MainWindow::slotNewTrackPlaying() { slotMetadataChanged( The::engineController()->currentTrack() ); } void MainWindow::slotMetadataChanged( Meta::TrackPtr track ) { if( track ) setPlainCaption( i18n( "%1 - %2 :: %3", track->artist() ? track->artist()->prettyName() : i18n( "Unknown" ), track->prettyName(), i18n( AMAROK_CAPTION ) ) ); } CollectionWidget * MainWindow::collectionBrowser() { return m_collectionBrowser; } QString MainWindow::activeBrowserName() { if( m_browserDock.data()->list()->activeCategory() ) return m_browserDock.data()->list()->activeCategory()->name(); else return QString(); } void MainWindow::setLayoutLocked( bool locked ) { DEBUG_BLOCK if( locked ) { m_browserDock.data()->setMovable( false ); m_contextDock.data()->setMovable( false ); m_playlistDock.data()->setMovable( false ); m_slimToolbar.data()->setFloatable( false ); m_slimToolbar.data()->setMovable( false ); m_mainToolbar.data()->setFloatable( false ); m_mainToolbar.data()->setMovable( false ); } else { m_browserDock.data()->setMovable( true ); m_contextDock.data()->setMovable( true ); m_playlistDock.data()->setMovable( true ); m_slimToolbar.data()->setFloatable( true ); m_slimToolbar.data()->setMovable( true ); m_mainToolbar.data()->setFloatable( true ); m_mainToolbar.data()->setMovable( true ); } AmarokConfig::setLockLayout( locked ); AmarokConfig::self()->writeConfig(); } void MainWindow::resetLayout() { // Store current state, so that we can undo the operation const QByteArray state = saveState(); // Remove all dock widgets, then add them again. This resets their state completely. removeDockWidget( m_browserDock.data() ); removeDockWidget( m_contextDock.data() ); removeDockWidget( m_playlistDock.data() ); addDockWidget( Qt::LeftDockWidgetArea, m_browserDock.data() ); addDockWidget( Qt::LeftDockWidgetArea, m_contextDock.data(), Qt::Horizontal ); addDockWidget( Qt::LeftDockWidgetArea, m_playlistDock.data(), Qt::Horizontal ); m_browserDock.data()->setFloating( false ); m_contextDock.data()->setFloating( false ); m_playlistDock.data()->setFloating( false ); m_browserDock.data()->show(); m_contextDock.data()->show(); m_playlistDock.data()->show(); // Now set Amarok's default dockwidget sizes setDefaultDockSizes(); if( KMessageBox::warningContinueCancel( this, i18n( "Apply this layout change?" ), i18n( "Reset Layout" ) ) == KMessageBox::Cancel ) restoreState( state ); } void MainWindow::setDefaultDockSizes() // SLOT { int totalWidgetWidth = contentsRect().width(); //get the width of the splitter handles, we need to subtract these... const int splitterHandleWidth = style()->pixelMetric( QStyle::PM_DockWidgetSeparatorExtent, 0, 0 ); totalWidgetWidth -= ( splitterHandleWidth * 2 ); const int widgetWidth = totalWidgetWidth / 3; const int leftover = totalWidgetWidth - 3 * widgetWidth; //We need to set fixed widths initially, just until the main window has been properly laid out. As soon as this has //happened, we will unlock these sizes again so that the elements can be resized by the user. const int mins[3] = { m_browserDock.data()->minimumWidth(), m_contextDock.data()->minimumWidth(), m_playlistDock.data()->minimumWidth() }; const int maxs[3] = { m_browserDock.data()->maximumWidth(), m_contextDock.data()->maximumWidth(), m_playlistDock.data()->maximumWidth() }; m_browserDock.data()->setFixedWidth( widgetWidth * 0.65 ); m_contextDock.data()->setFixedWidth( widgetWidth * 1.7 + leftover ); m_playlistDock.data()->setFixedWidth( widgetWidth * 0.65 ); // Important: We need to activate the layout we have just set layout()->activate(); m_browserDock.data()->setMinimumWidth( mins[0] ); m_browserDock.data()->setMaximumWidth( maxs[0] ); m_contextDock.data()->setMinimumWidth( mins[1] ); m_contextDock.data()->setMaximumWidth( maxs[1] ); m_playlistDock.data()->setMinimumWidth( mins[2] ); m_playlistDock.data()->setMaximumWidth( maxs[2] ); } bool MainWindow::playAudioCd() { DEBUG_BLOCK //drop whatever we are doing and play auidocd QList collections = CollectionManager::instance()->viewableCollections(); // Search a non-empty MemoryCollection with the id: AudioCd foreach( Collections::Collection *collection, collections ) { if( collection->collectionId() == "AudioCd" ) { debug() << "got audiocd collection"; Collections::MemoryCollection * cdColl = dynamic_cast( collection ); if( !cdColl || cdColl->trackMap().count() == 0 ) { debug() << "cd collection not ready yet (track count = 0 )"; m_waitingForCd = true; return false; } The::playlistController()->insertOptioned( cdColl->trackMap().values(), Playlist::OnPlayMediaAction ); m_waitingForCd = false; return true; } } debug() << "waiting for cd..."; m_waitingForCd = true; return false; } bool MainWindow::isWaitingForCd() const { DEBUG_BLOCK debug() << "waiting?: " << m_waitingForCd; return m_waitingForCd; } bool MainWindow::isOnCurrentDesktop() const { #ifdef Q_WS_X11 return KWindowSystem::windowInfo( winId(), NET::WMDesktop ).desktop() == KWindowSystem::currentDesktop(); #else return true; #endif } diff --git a/src/OpmlParser.cpp b/src/OpmlParser.cpp index 854d236a0d..13fde6e869 100644 --- a/src/OpmlParser.cpp +++ b/src/OpmlParser.cpp @@ -1,429 +1,429 @@ /**************************************************************************************** * Copyright (c) 2010 Bart Cerneels * * 2009 Mathias Panzenböck * * * * 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, see . * ****************************************************************************************/ #include "OpmlParser.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include #include #include #include const QString OpmlParser::OPML_MIME = "text/x-opml+xml"; const OpmlParser::StaticData OpmlParser::sd; -OpmlParser::OpmlParser( const KUrl &url ) +OpmlParser::OpmlParser( const QUrl &url ) : ThreadWeaver::Job() , QXmlStreamReader() , m_url( url ) { } OpmlParser::~OpmlParser() { } void OpmlParser::run() { read( m_url ); } bool -OpmlParser::read( const KUrl &url ) +OpmlParser::read( const QUrl &url ) { m_url = url; if( m_url.isLocalFile() ) { //read directly from local file QFile localFile( m_url.toLocalFile() ); if( !localFile.open( QIODevice::ReadOnly ) ) { debug() << "failed to open local OPML file " << m_url.url(); return false; } return read( &localFile ); } m_transferJob = KIO::get( m_url, KIO::Reload, KIO::HideProgressInfo ); connect( m_transferJob, SIGNAL(data(KIO::Job*,QByteArray)), SLOT(slotAddData(KIO::Job*,QByteArray)) ); connect( m_transferJob, SIGNAL(result(KJob*)), SLOT(downloadResult(KJob*)) ); // parse data return read(); } bool OpmlParser::read( QIODevice *device ) { setDevice( device ); return read(); } void OpmlParser::slotAddData( KIO::Job *job, const QByteArray &data ) { Q_UNUSED( job ) QXmlStreamReader::addData( data ); // parse more data continueRead(); } void OpmlParser::downloadResult( KJob *job ) { // parse more data continueRead(); KIO::TransferJob *transferJob = dynamic_cast( job ); if( job->error() || ( transferJob && transferJob->isErrorPage() ) ) { QString errorMessage = i18n( "Reading OPML podcast from %1 failed with error:\n", m_url.url() ); errorMessage = errorMessage.append( job->errorString() ); // emit statusBarSorryMessage( errorMessage ); } m_transferJob = 0; } void OpmlParser::slotAbort() { DEBUG_BLOCK } void OpmlParser::Action::begin( OpmlParser *opmlParser ) const { if( m_begin ) (( *opmlParser ).*m_begin )(); } void OpmlParser::Action::end( OpmlParser *opmlParser ) const { if( m_end ) (( *opmlParser ).*m_end )(); } void OpmlParser::Action::characters( OpmlParser *opmlParser ) const { if( m_characters ) (( *opmlParser ).*m_characters )(); } // initialization of the feed parser automata: OpmlParser::StaticData::StaticData() : startAction( rootMap ) , docAction( docMap, 0, &OpmlParser::endDocument ) , skipAction( skipMap ) , noContentAction( noContentMap, &OpmlParser::beginNoElement, 0, &OpmlParser::readNoCharacters ) , opmlAction( opmlMap, &OpmlParser::beginOpml ) , headAction( headMap, 0, &OpmlParser::endHead ) , titleAction( textMap, &OpmlParser::beginText, &OpmlParser::endTitle, &OpmlParser::readCharacters ) , bodyAction( bodyMap ) , outlineAction( outlineMap, &OpmlParser::beginOutline, &OpmlParser::endOutline ) { // known elements: knownElements[ "opml" ] = Opml; knownElements[ "html" ] = Html; knownElements[ "HTML" ] = Html; knownElements[ "head" ] = Head; knownElements[ "title" ] = Title; knownElements[ "dateCreated" ] = DateCreated; knownElements[ "dateModified" ] = DateModified; knownElements[ "ownerName" ] = OwnerName; knownElements[ "ownerEmail" ] = OwnerEmail; knownElements[ "ownerId" ] = OwnerId; knownElements[ "docs" ] = Docs; knownElements[ "expansionState" ] = ExpansionState; knownElements[ "vertScrollState" ] = VertScrollState; knownElements[ "windowTop" ] = WindowTop; knownElements[ "windowLeft" ] = WindowLeft; knownElements[ "windowBottom" ] = WindowBottom; knownElements[ "windowRight" ] = WindowRight; knownElements[ "body" ] = Body; knownElements[ "outline" ] = Outline; // before start document/after end document rootMap.insert( Document, &docAction ); // parse document docMap.insert( Opml, &opmlAction ); // docMap.insert( Html, &htmlAction ); // parse opmlMap.insert( Head, &headAction ); opmlMap.insert( Body, &bodyAction ); // parse headMap.insert( Title, &titleAction ); headMap.insert( DateCreated, &skipAction ); headMap.insert( DateModified, &skipAction ); headMap.insert( OwnerName, &skipAction ); headMap.insert( OwnerEmail, &skipAction ); headMap.insert( OwnerId, &skipAction ); headMap.insert( Docs, &skipAction ); headMap.insert( ExpansionState, &skipAction ); headMap.insert( VertScrollState, &skipAction ); headMap.insert( WindowTop, &skipAction ); headMap.insert( WindowLeft, &skipAction ); headMap.insert( WindowBottom, &skipAction ); headMap.insert( WindowRight, &skipAction ); // parse bodyMap.insert( Outline, &outlineAction ); // parse in case of sub-elements outlineMap.insert( Outline, &outlineAction ); // skip elements skipMap.insert( Any, &skipAction ); } OpmlParser::ElementType OpmlParser::elementType() const { if( isEndDocument() || isStartDocument() ) return Document; if( isCDATA() || isCharacters() ) return CharacterData; ElementType elementType = sd.knownElements[ QXmlStreamReader::name().toString()]; return elementType; } bool OpmlParser::read() { m_buffer.clear(); m_actionStack.clear(); m_actionStack.push( &( OpmlParser::sd.startAction ) ); setNamespaceProcessing( false ); return continueRead(); } bool OpmlParser::continueRead() { // this is some kind of pushdown automata // with this it should be possible to parse feeds in parallel // without using threads DEBUG_BLOCK while( !atEnd() && error() != CustomError ) { TokenType token = readNext(); if( error() == PrematureEndOfDocumentError && m_transferJob ) return true; if( hasError() ) { emit doneParsing(); return false; } if( m_actionStack.isEmpty() ) { debug() << "expected element on stack!"; return false; } const Action* action = m_actionStack.top(); const Action* subAction = 0; switch( token ) { case Invalid: { debug() << "invalid token received at line " << lineNumber(); debug() << "Error:\n" << errorString(); return false; } case StartDocument: case StartElement: subAction = action->actionMap()[ elementType() ]; if( !subAction ) subAction = action->actionMap()[ Any ]; if( !subAction ) subAction = &( OpmlParser::sd.skipAction ); m_actionStack.push( subAction ); subAction->begin( this ); break; case EndDocument: case EndElement: action->end( this ); if( m_actionStack.pop() != action ) { debug() << "popped other element than expected!"; } break; case Characters: if( !isWhitespace() || isCDATA() ) { action->characters( this ); } // ignoreable whitespaces case Comment: case EntityReference: case ProcessingInstruction: case DTD: case NoToken: // ignore break; } } return !hasError(); } void OpmlParser::stopWithError( const QString &message ) { raiseError( message ); if( m_transferJob ) { m_transferJob->kill( KJob::EmitResult ); m_transferJob = 0; } emit doneParsing(); } void OpmlParser::beginOpml() { m_outlineStack.clear(); } void OpmlParser::beginText() { m_buffer.clear(); } void OpmlParser::beginOutline() { OpmlOutline *parent = m_outlineStack.empty() ? 0 : m_outlineStack.top(); OpmlOutline *outline = new OpmlOutline( parent ); //adding outline to stack m_outlineStack.push( outline ); if( parent ) { parent->setHasChildren( true ); parent->addChild( outline ); } foreach( const QXmlStreamAttribute &attribute, attributes() ) outline->addAttribute( attribute.name().toString(), attribute.value().toString() ); emit outlineParsed( outline ); } void OpmlParser::beginNoElement() { debug() << "no element expected here, but got element: " << QXmlStreamReader::name(); } void OpmlParser::endDocument() { emit doneParsing(); } void OpmlParser::endHead() { emit headerDone(); } void OpmlParser::endTitle() { m_headerData.insert( "title", m_buffer.trimmed() ); } void OpmlParser::endOutline() { OpmlOutline *outline = m_outlineStack.pop(); if( m_outlineStack.isEmpty() ) m_outlines << outline; } void OpmlParser::readCharacters() { m_buffer += text(); } void OpmlParser::readNoCharacters() { DEBUG_BLOCK debug() << "no characters expected here"; } diff --git a/src/OpmlParser.h b/src/OpmlParser.h index fd57c7efa2..0010cb1fda 100644 --- a/src/OpmlParser.h +++ b/src/OpmlParser.h @@ -1,271 +1,271 @@ /**************************************************************************************** * Copyright (c) 2008 Nikolaj Hald Nielsen * * Copyright (c) 2009 Bart Cerneels * * * * 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, see . * ****************************************************************************************/ #ifndef OPMLPARSER_H #define OPMLPARSER_H #include "amarok_export.h" #include "OpmlOutline.h" #include #include #include #include #include #include #include -#include +#include #include namespace KIO { class Job; class TransferJob; } /** * Parser for OPML files. */ class AMAROK_EXPORT OpmlParser : public ThreadWeaver::Job, public QXmlStreamReader { Q_OBJECT public: static const QString OPML_MIME; /** * Constructor * @param fileName The file to parse * @return Pointer to new object */ - OpmlParser( const KUrl &url ); + OpmlParser( const QUrl &url ); /** * Destructor * @return none */ ~OpmlParser(); /** * The function that starts the actual work. Inherited from ThreadWeaver::Job * Note the work is performed in a separate thread * @return Returns true on success and false on failure */ void run(); - bool read( const KUrl &url ); + bool read( const QUrl &url ); bool read( QIODevice *device ); /** @return the URL of the OPML being parsed. */ - KUrl url() const { return m_url; } + QUrl url() const { return m_url; } QMap headerData() { return m_headerData; } /** * Get the result of the parsing as a list of OpmlOutlines. * This list contains only root outlines that can be found in the of the OPML. * The rest are children of these root items. * * The user is responsible for deleting the results. */ QList results() const { return m_outlines; } signals: /** * Emitted when has been completely parsed. */ void headerDone(); /** * Signal emmited when parsing is complete. * The data is complete now and accessible via results(). * Children of all the outlines are available via OpmlOutline::children(). */ void doneParsing(); /** * Emitted when a new outline item is available. * Emitted after the attributes have been read but before any of the children are available. * Each child will be reported in a separate signal. */ void outlineParsed( OpmlOutline *outline ); public slots: virtual void slotAbort(); private slots: void slotAddData( KIO::Job *, const QByteArray &data ); void downloadResult( KJob * ); private: enum ElementType { Unknown = 0, Any, Document, CharacterData, Opml, Html, Head, Title, DateCreated, DateModified, OwnerName, OwnerEmail, OwnerId, Docs, ExpansionState, VertScrollState, WindowTop, WindowLeft, WindowBottom, WindowRight, Body, Outline }; class Action; typedef void (OpmlParser::*ActionCallback)(); typedef QHash ActionMap; class Action { public: Action( ActionMap &actionMap ) : m_actionMap( actionMap ) , m_begin( 0 ) , m_end( 0 ) , m_characters( 0 ) {} Action(ActionMap &actionMap, ActionCallback begin) : m_actionMap( actionMap ) , m_begin( begin ) , m_end( 0 ) , m_characters( 0 ) {} Action(ActionMap &actionMap, ActionCallback begin, ActionCallback end) : m_actionMap( actionMap ) , m_begin( begin ) , m_end( end ) , m_characters( 0 ) {} Action(ActionMap &actionMap, ActionCallback begin, ActionCallback end, ActionCallback characters) : m_actionMap( actionMap ) , m_begin( begin ) , m_end( end ) , m_characters( characters ) {} void begin( OpmlParser *opmlParser ) const; void end( OpmlParser *opmlParser ) const; void characters( OpmlParser *opmlParser ) const; const ActionMap &actionMap() const { return m_actionMap; } private: ActionMap &m_actionMap; ActionCallback m_begin; ActionCallback m_end; ActionCallback m_characters; }; ElementType elementType() const; bool read(); bool continueRead(); // callback methods for parsing void beginOpml(); void beginText(); void beginOutline(); void beginNoElement(); void endDocument(); void endHead(); void endTitle(); void endOutline(); void readCharacters(); void readNoCharacters(); void stopWithError( const QString &message ); class StaticData { public: StaticData(); // This here basically builds an automata. // This way feed parsing can be paused after any token, // thus enabling parallel download and parsing of multiple // feeds without the need for threads. QHash knownElements; //Actions Action startAction; Action docAction; Action skipAction; Action noContentAction; Action opmlAction; Action headAction; Action titleAction; // Action dateCreatedAction; // Action dateModifiedAction; // Action ownerNameAction; // Action ownerEmailAction; // Action ownerIdAction; // Action docsAction; // Action expansionStateAction; Action bodyAction; Action outlineAction; ActionMap rootMap; ActionMap skipMap; ActionMap noContentMap; ActionMap xmlMap; ActionMap docMap; ActionMap opmlMap; ActionMap headMap; ActionMap bodyMap; ActionMap outlineMap; ActionMap textMap; }; static const StaticData sd; QStack m_actionStack; QString m_buffer; QMap m_headerData; // the top level outlines of . QList m_outlines; // currently processing outlines so we can do nested outlines. QStack m_outlineStack; - KUrl m_url; + QUrl m_url; KIO::TransferJob *m_transferJob; }; #endif diff --git a/src/OpmlWriter.cpp b/src/OpmlWriter.cpp index e70ad5620a..32436e47df 100644 --- a/src/OpmlWriter.cpp +++ b/src/OpmlWriter.cpp @@ -1,77 +1,77 @@ /**************************************************************************************** * Copyright (c) 2010 Bart Cerneels * * * * 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, see . * ****************************************************************************************/ #include "OpmlWriter.h" #include "core/support/Debug.h" -#include +#include OpmlWriter::OpmlWriter( const QList rootOutlines, const QMap headerData, QIODevice *device ) : ThreadWeaver::Job() , m_rootOutlines( rootOutlines ) , m_headerData( headerData ) { m_xmlWriter = new QXmlStreamWriter( device ); } void OpmlWriter::run() { #define _x m_xmlWriter _x->setAutoFormatting( true ); _x->writeStartDocument(); _x->writeStartElement( "opml" ); _x->writeAttribute( "version", "2.0" ); _x->writeStartElement( "head" ); QMapIterator ai( m_headerData ); //attributesIterator while( ai.hasNext() ) { ai.next(); _x->writeTextElement( ai.key(), ai.value() ); } _x->writeEndElement(); // head _x->writeStartElement( "body" ); foreach( const OpmlOutline *childOutline, m_rootOutlines ) writeOutline( childOutline ); _x->writeEndDocument(); //implicitly closes all open tags (opml & body) emit result( 0 ); } void OpmlWriter::writeOutline( const OpmlOutline *outline ) { bool hasChildren = outline->children().count() != 0; if( hasChildren && ( outline->opmlNodeType() != IncludeNode ) ) _x->writeStartElement( "outline" ); else _x->writeEmptyElement( "outline" ); QMapIterator ai( outline->attributes() ); // attributesIterator while( ai.hasNext() ) { ai.next(); _x->writeAttribute( ai.key(), ai.value() ); } // children of expanded include nodes should not be saved. if( hasChildren && ( outline->opmlNodeType() != IncludeNode ) ) { foreach( const OpmlOutline *childOutline, outline->children() ) writeOutline( childOutline ); _x->writeEndElement(); // outline } } diff --git a/src/OpmlWriter.h b/src/OpmlWriter.h index dc162e089d..19033c7a96 100644 --- a/src/OpmlWriter.h +++ b/src/OpmlWriter.h @@ -1,67 +1,67 @@ /**************************************************************************************** * Copyright (c) 2010 Bart Cerneels * * * * 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, see . * ****************************************************************************************/ #ifndef OPMLWRITER_H #define OPMLWRITER_H #include "OpmlOutline.h" #include -#include +#include #include class AMAROK_EXPORT OpmlWriter : public ThreadWeaver::Job { Q_OBJECT public: /** OpmlWriter will write the OPML outline objects as XML text. * @arg rootOutlines the of the OPML * @arg headerData these fields are put in the of the OPML * @arg device QIODevice to write to * The children of IncludeNodes will not be written. Remove the type="include" attribute * from the include node to force a save of those child nodes. */ OpmlWriter( const QList rootOutlines, const QMap headerData, QIODevice *device ); void setHeaderData( const QMap data ) { m_headerData = data; } /** * The function that starts the actual work. Inherited from ThreadWeaver::Job * Note the work is performed in a separate thread * @return Returns true on success and false on failure */ void run(); QIODevice *device() { return m_xmlWriter->device(); } signals: /** * Signal emmited when writing is complete. */ void result( int error ); private: void writeOutline( const OpmlOutline *outline ); QList m_rootOutlines; QMap m_headerData; - KUrl m_fileUrl; + QUrl m_fileUrl; QXmlStreamWriter *m_xmlWriter; }; #endif // OPMLWRITER_H diff --git a/src/aboutdialog/OcsPersonItem.cpp b/src/aboutdialog/OcsPersonItem.cpp index d239c2cfbf..25c80c6025 100644 --- a/src/aboutdialog/OcsPersonItem.cpp +++ b/src/aboutdialog/OcsPersonItem.cpp @@ -1,352 +1,352 @@ /**************************************************************************************** * Copyright (c) 2009 Téo Mrnjavac * * * * 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, see . * ****************************************************************************************/ #include "OcsPersonItem.h" #include "core/support/Debug.h" #include "libattica-ocsclient/provider.h" #include "libattica-ocsclient/providerinitjob.h" #include "libattica-ocsclient/personjob.h" #include #include #include #include #include #include #include OcsPersonItem::OcsPersonItem( const KAboutPerson &person, const QString ocsUsername, PersonStatus status, QWidget *parent ) : QWidget( parent ) , m_status( status ) , m_state( Offline ) { m_person = &person; m_ocsUsername = ocsUsername; setupUi( this ); init(); m_avatar->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); //TODO: Add favorite artists! } void OcsPersonItem::init() { m_textLabel->setTextInteractionFlags( Qt::TextBrowserInteraction ); m_textLabel->setOpenExternalLinks( true ); m_textLabel->setContentsMargins( 5, 0, 0, 2 ); m_verticalLayout->setSpacing( 0 ); m_vertLine->hide(); m_initialSpacer->changeSize( 0, 40, QSizePolicy::Fixed, QSizePolicy::Fixed ); layout()->invalidate(); m_aboutText.append( "" + m_person->name() + "" ); if( !m_person->task().isEmpty() ) m_aboutText.append( "
" + m_person->task() ); m_iconsBar = new KToolBar( this, false, false ); m_snBar = new KToolBar( this, false, false ); m_iconsBar->setIconSize( QSize( 22, 22 ) ); m_iconsBar->setContentsMargins( 0, 0, 0, 0 ); m_iconsBar->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum ); if( m_status == Author ) { QHBoxLayout *iconsLayout = new QHBoxLayout( this ); iconsLayout->setMargin( 0 ); iconsLayout->setSpacing( 0 ); m_verticalLayout->insertLayout( m_verticalLayout->count() - 1, iconsLayout ); iconsLayout->addWidget( m_iconsBar ); iconsLayout->addWidget( m_snBar ); iconsLayout->addStretch( 0 ); m_snBar->setIconSize( QSize( 16, 16 ) ); m_snBar->setContentsMargins( 0, 0, 0, 0 ); m_snBar->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum ); } else { layout()->addWidget( m_iconsBar ); m_snBar->hide(); } if( !m_person->emailAddress().isEmpty() ) { KAction *email = new KAction( KIcon( "internet-mail" ), i18n("Email contributor"), this ); email->setToolTip( m_person->emailAddress() ); email->setData( QString( "mailto:" + m_person->emailAddress() ) ); m_iconsBar->addAction( email ); } if( !m_person->webAddress().isEmpty() ) { KAction *homepage = new KAction( KIcon( "applications-internet" ), i18n("Visit contributor's homepage"), this ); homepage->setToolTip( m_person->webAddress() ); homepage->setData( m_person->webAddress() ); m_iconsBar->addAction( homepage ); } connect( m_iconsBar, SIGNAL(actionTriggered(QAction*)), this, SLOT(launchUrl(QAction*)) ); connect( m_snBar, SIGNAL(actionTriggered(QAction*)), this, SLOT(launchUrl(QAction*)) ); m_textLabel->setText( m_aboutText ); } OcsPersonItem::~OcsPersonItem() {} QString OcsPersonItem::name() { return m_person->name(); } void OcsPersonItem::launchUrl( QAction *action ) //SLOT { - KUrl url = KUrl( action->data().toString() ); + QUrl url = QUrl( action->data().toString() ); KRun::runUrl( url, "text/html", 0, false ); } void OcsPersonItem::switchToOcs( const AmarokAttica::Provider &provider ) { if( m_state == Online ) return; m_avatar->setFixedWidth( 56 ); m_vertLine->show(); m_initialSpacer->changeSize( 5, 40, QSizePolicy::Fixed, QSizePolicy::Fixed ); layout()->invalidate(); if( !m_ocsUsername.isEmpty() ) { AmarokAttica::PersonJob *personJob; if( m_ocsUsername == QString( "%%category%%" ) ) //TODO: handle grouping return; personJob = provider.requestPerson( m_ocsUsername ); connect( personJob, SIGNAL(result(KJob*)), this, SLOT(onJobFinished(KJob*)) ); emit ocsFetchStarted(); m_state = Online; } } void OcsPersonItem::onJobFinished( KJob *job ) { AmarokAttica::PersonJob *personJob = qobject_cast< AmarokAttica::PersonJob * >( job ); if( personJob->error() == 0 ) { fillOcsData( personJob->person() ); } emit ocsFetchResult( personJob->error() ); } void OcsPersonItem::fillOcsData( const AmarokAttica::Person &ocsPerson ) { if( !( ocsPerson.avatar().isNull() ) ) { m_avatar->setFixedSize( 56, 56 ); m_avatar->setFrameShape( QFrame::StyledPanel ); //this is a FramedLabel, otherwise oxygen wouldn't paint the frame m_avatar->setPixmap( ocsPerson.avatar() ); m_avatar->setAlignment( Qt::AlignCenter ); } if( !ocsPerson.country().isEmpty() ) { m_aboutText.append( "
" ); if( !ocsPerson.city().isEmpty() ) m_aboutText.append( i18nc( "A person's location: City, Country", "%1, %2", ocsPerson.city(), ocsPerson.country() ) ); else m_aboutText.append( ocsPerson.country() ); } if( m_status == Author ) { if( !ocsPerson.extendedAttribute( "ircchannels" ).isEmpty() ) { QString channelsString = ocsPerson.extendedAttribute( "ircchannels" ); //We extract the channel names from the string provided by OCS: QRegExp channelrx = QRegExp( "#+[\\w\\.\\-\\/!()+]+([\\w\\-\\/!()+]?)", Qt::CaseInsensitive ); QStringList channels; int pos = 0; while( ( pos = channelrx.indexIn( channelsString, pos ) ) != -1 ) { channels << channelrx.cap( 0 ); pos += channelrx.matchedLength(); } m_aboutText.append( "
" + i18n("IRC channels: ") ); QString link; foreach( const QString &channel, channels ) { const QString channelName = QString( channel ).remove( '#' ); link = QString( "irc://irc.freenode.org/%1" ).arg( channelName ); m_aboutText.append( QString( "%2" ).arg( link, channel ) + " " ); } } if( !ocsPerson.extendedAttribute( "favouritemusic" ).isEmpty() ) { QStringList artists = ocsPerson.extendedAttribute( "favouritemusic" ).split( ", " ); //TODO: make them clickable m_aboutText.append( "
" + i18n( "Favorite music: " ) + artists.join( ", " ) ); } } KAction *visitProfile = new KAction( KIcon( QPixmap( KStandardDirs::locate( "data", "amarok/images/opendesktop-22.png" ) ) ), i18n( "Visit %1's openDesktop.org profile", ocsPerson.firstName() ), this ); visitProfile->setToolTip( i18n( "Visit %1's profile on openDesktop.org", ocsPerson.firstName() ) ); visitProfile->setData( ocsPerson.extendedAttribute( "profilepage" ) ); m_iconsBar->addAction( visitProfile ); if( m_status == Author ) { QList< QPair< QString, QString > > ocsHomepages; ocsHomepages.append( QPair< QString, QString >( ocsPerson.extendedAttribute( "homepagetype" ), ocsPerson.homepage() ) ); debug() << "USER HOMEPAGE DATA STARTS HERE"; debug() << ocsHomepages.last().first << " :: " << ocsHomepages.last().second; for( int i = 2; i <= 10; i++ ) //OCS supports 10 total homepages as of 2/oct/2009 { QString type = ocsPerson.extendedAttribute( QString( "homepagetype%1" ).arg( i ) ); ocsHomepages.append( QPair< QString, QString >( ( type == " " ) ? "" : type, ocsPerson.extendedAttribute( QString( "homepage%1" ).arg( i ) ) ) ); debug() << ocsHomepages.last().first << " :: " << ocsHomepages.last().second; } bool fillHomepageFromOcs = m_person->webAddress().isEmpty(); //We check if the person already has a homepage in KAboutPerson. for( QList< QPair< QString, QString > >::const_iterator entry = ocsHomepages.constBegin(); entry != ocsHomepages.constEnd(); ++entry ) { QString type = (*entry).first; QString url = (*entry).second; KIcon icon; QString text; if( type == "Blog" ) { icon = KIcon( "kblogger" ); text = i18n( "Visit contributor's blog" ); } else if( type == "delicious" ) { icon = KIcon( QPixmap( KStandardDirs::locate( "data", "amarok/images/emblem-delicious.png" ) ) ); text = i18n( "Visit contributor's del.icio.us profile" ); } else if( type == "Digg" ) { icon = KIcon( QPixmap( KStandardDirs::locate( "data", "amarok/images/emblem-digg.png" ) ) ); text = i18n( "Visit contributor's Digg profile" ); } else if( type == "Facebook" ) { icon = KIcon( QPixmap( KStandardDirs::locate( "data", "amarok/images/emblem-facebook.png" ) ) ); text = i18n( "Visit contributor's Facebook profile" ); } else if( type == "Homepage" || type == "other" || ( type.isEmpty() && !url.isEmpty() ) ) { if( fillHomepageFromOcs ) { KAction *homepage = new KAction( KIcon( "applications-internet" ), i18n("Visit contributor's homepage"), this ); homepage->setToolTip( url ); homepage->setData( url ); m_iconsBar->addAction( homepage ); fillHomepageFromOcs = false; continue; } if( type == "other" && url.contains( "last.fm/" ) ) //HACK: assign a last.fm icon if the URL contains last.fm { icon = KIcon( QPixmap( KStandardDirs::locate( "data", "amarok/images/emblem-lastfm.png" ) ) ); text = i18n( "Visit contributor's Last.fm profile" ); } else continue; } else if( type == "LinkedIn" ) { icon = KIcon( QPixmap( KStandardDirs::locate( "data", "amarok/images/emblem-linkedin.png" ) ) ); text = i18n( "Visit contributor's LinkedIn profile" ); } else if( type == "MySpace" ) { icon = KIcon( QPixmap( KStandardDirs::locate( "data", "amarok/images/emblem-myspace.png" ) ) ); text = i18n( "Visit contributor's MySpace homepage" ); } else if( type == "Reddit" ) { icon = KIcon( QPixmap( KStandardDirs::locate( "data", "amarok/images/emblem-reddit.png" ) ) ); text = i18n( "Visit contributor's Reddit profile" ); } else if( type == "YouTube" ) { icon = KIcon( "dragonplayer" ); //FIXME: icon text = i18n( "Visit contributor's YouTube profile" ); } else if( type == "Twitter" ) { icon = KIcon( QPixmap( KStandardDirs::locate( "data", "amarok/images/emblem-twitter.png" ) ) ); text = i18n( "Visit contributor's Twitter feed" ); } else if( type == "Wikipedia" ) { icon = KIcon( QPixmap( KStandardDirs::locate( "data", "amarok/images/emblem-wikipedia.png" ) ) ); text = i18n( "Visit contributor's Wikipedia profile" ); } else if( type == "Xing" ) { icon = KIcon( QPixmap( KStandardDirs::locate( "data", "amarok/images/emblem-xing.png" ) ) ); text = i18n( "Visit contributor's Xing profile" ); } else if( type == "identi.ca" ) { icon = KIcon( QPixmap( KStandardDirs::locate( "data", "amarok/images/emblem-identica.png" ) ) ); text = i18n( "Visit contributor's identi.ca feed" ); } else if( type == "libre.fm" ) { icon = KIcon( "juk" ); //FIXME: icon text = i18n( "Visit contributor's libre.fm profile" ); } else if( type == "StackOverflow" ) { icon = KIcon( QPixmap( KStandardDirs::locate( "data", "amarok/images/emblem-stackoverflow.png" ) ) ); text = i18n( "Visit contributor's StackOverflow profile" ); } else break; KAction *action = new KAction( icon, text, this ); action->setToolTip( url ); action->setData( url ); m_snBar->addAction( action ); } debug() << "END USER HOMEPAGE DATA"; } m_textLabel->setText( m_aboutText ); } diff --git a/src/aboutdialog/libattica-ocsclient/activitylistjob.cpp b/src/aboutdialog/libattica-ocsclient/activitylistjob.cpp index eee82697b0..1ca91e9f48 100644 --- a/src/aboutdialog/libattica-ocsclient/activitylistjob.cpp +++ b/src/aboutdialog/libattica-ocsclient/activitylistjob.cpp @@ -1,87 +1,87 @@ /* This file is part of KDE. Copyright (c) 2008 Cornelius Schumacher 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "activitylistjob.h" #include "activityparser.h" #include #include #include #include using namespace AmarokAttica; ActivityListJob::ActivityListJob() : m_job( 0 ) { } -void ActivityListJob::setUrl( const KUrl &url ) +void ActivityListJob::setUrl( const QUrl &url ) { m_url = url; } void ActivityListJob::start() { QTimer::singleShot( 0, this, SLOT(doWork()) ); } Activity::List ActivityListJob::ActivityList() const { return m_activityList; } void ActivityListJob::doWork() { qDebug() << m_url; m_job = KIO::get( m_url, KIO::NoReload, KIO::HideProgressInfo ); connect( m_job, SIGNAL(result(KJob*)), SLOT(slotJobResult(KJob*)) ); connect( m_job, SIGNAL(data(KIO::Job*,QByteArray)), SLOT(slotJobData(KIO::Job*,QByteArray)) ); } void ActivityListJob::slotJobResult( KJob *job ) { m_job = 0; if ( job->error() ) { setError( job->error() ); setErrorText( job->errorText() ); emitResult(); } else { // qDebug() << m_data; m_activityList = ActivityParser().parseList( QString::fromUtf8( m_data.data() ) ); emitResult(); } } void ActivityListJob::slotJobData( KIO::Job *, const QByteArray &data ) { m_data.append( data ); } diff --git a/src/aboutdialog/libattica-ocsclient/activitylistjob.h b/src/aboutdialog/libattica-ocsclient/activitylistjob.h index 8f41b9aed9..148d243aa5 100644 --- a/src/aboutdialog/libattica-ocsclient/activitylistjob.h +++ b/src/aboutdialog/libattica-ocsclient/activitylistjob.h @@ -1,64 +1,64 @@ /* This file is part of KDE. Copyright (c) 2008 Cornelius Schumacher 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef ATTICA_ACTIVITYLISTJOB_H #define ATTICA_ACTIVITYLISTJOB_H #include -#include +#include #include "activity.h" namespace KIO { class Job; } namespace AmarokAttica { class ATTICA_EXPORT ActivityListJob : public KJob { Q_OBJECT public: ActivityListJob(); - void setUrl( const KUrl & ); + void setUrl( const QUrl & ); void start(); Activity::List ActivityList() const; protected slots: void doWork(); void slotJobResult( KJob *job ); void slotJobData( KIO::Job *job, const QByteArray &data ); private: - KUrl m_url; + QUrl m_url; KIO::Job *m_job; QByteArray m_data; Activity::List m_activityList; }; } #endif diff --git a/src/aboutdialog/libattica-ocsclient/categorylistjob.cpp b/src/aboutdialog/libattica-ocsclient/categorylistjob.cpp index 0ef87d69b6..c594dccbda 100644 --- a/src/aboutdialog/libattica-ocsclient/categorylistjob.cpp +++ b/src/aboutdialog/libattica-ocsclient/categorylistjob.cpp @@ -1,87 +1,87 @@ /* This file is part of KDE. Copyright (c) 2008 Cornelius Schumacher 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "categorylistjob.h" #include "categoryparser.h" #include #include #include #include using namespace AmarokAttica; CategoryListJob::CategoryListJob() : m_job( 0 ) { } -void CategoryListJob::setUrl( const KUrl &url ) +void CategoryListJob::setUrl( const QUrl &url ) { m_url = url; } void CategoryListJob::start() { QTimer::singleShot( 0, this, SLOT(doWork()) ); } Category::List CategoryListJob::categoryList() const { return m_categoryList; } void CategoryListJob::doWork() { qDebug() << m_url; m_job = KIO::get( m_url, KIO::NoReload, KIO::HideProgressInfo ); connect( m_job, SIGNAL(result(KJob*)), SLOT(slotJobResult(KJob*)) ); connect( m_job, SIGNAL(data(KIO::Job*,QByteArray)), SLOT(slotJobData(KIO::Job*,QByteArray)) ); } void CategoryListJob::slotJobResult( KJob *job ) { m_job = 0; if ( job->error() ) { setError( job->error() ); setErrorText( job->errorText() ); emitResult(); } else { qDebug() << m_data; m_categoryList = CategoryParser().parseList( QString::fromUtf8( m_data.data() ) ); emitResult(); } } void CategoryListJob::slotJobData( KIO::Job *, const QByteArray &data ) { m_data.append( data ); } diff --git a/src/aboutdialog/libattica-ocsclient/categorylistjob.h b/src/aboutdialog/libattica-ocsclient/categorylistjob.h index 72dcf10bd7..21f7174ec0 100644 --- a/src/aboutdialog/libattica-ocsclient/categorylistjob.h +++ b/src/aboutdialog/libattica-ocsclient/categorylistjob.h @@ -1,63 +1,63 @@ /* This file is part of KDE. Copyright (c) 2008 Cornelius Schumacher 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef ATTICA_CATEGORYLISTJOB_H #define ATTICA_CATEGORYLISTJOB_H #include "category.h" #include -#include +#include namespace KIO { class Job; } namespace AmarokAttica { class ATTICA_EXPORT CategoryListJob : public KJob { Q_OBJECT public: CategoryListJob(); - void setUrl( const KUrl & ); + void setUrl( const QUrl & ); void start(); Category::List categoryList() const; protected slots: void doWork(); void slotJobResult( KJob *job ); void slotJobData( KIO::Job *job, const QByteArray &data ); private: - KUrl m_url; + QUrl m_url; KIO::Job *m_job; QByteArray m_data; Category::List m_categoryList; }; } #endif diff --git a/src/aboutdialog/libattica-ocsclient/contentjob.cpp b/src/aboutdialog/libattica-ocsclient/contentjob.cpp index 40211c1376..c083b683c6 100644 --- a/src/aboutdialog/libattica-ocsclient/contentjob.cpp +++ b/src/aboutdialog/libattica-ocsclient/contentjob.cpp @@ -1,84 +1,84 @@ /* This file is part of KDE. Copyright (c) 2008 Cornelius Schumacher 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "contentjob.h" #include "contentparser.h" #include #include #include #include using namespace AmarokAttica; ContentJob::ContentJob() : m_job( 0 ) { } -void ContentJob::setUrl( const KUrl &url ) +void ContentJob::setUrl( const QUrl &url ) { m_url = url; } void ContentJob::start() { QTimer::singleShot( 0, this, SLOT(doWork()) ); } Content ContentJob::content() const { return m_content; } void ContentJob::doWork() { qDebug() << m_url; m_job = KIO::get( m_url, KIO::NoReload, KIO::HideProgressInfo ); connect( m_job, SIGNAL(result(KJob*)), SLOT(slotJobResult(KJob*)) ); connect( m_job, SIGNAL(data(KIO::Job*,QByteArray)), SLOT(slotJobData(KIO::Job*,QByteArray)) ); } void ContentJob::slotJobResult( KJob *job ) { m_job = 0; if ( job->error() ) { setError( job->error() ); setErrorText( job->errorText() ); } else { qDebug() << m_data; m_content = ContentParser().parse( QString::fromUtf8( m_data.data() ) ); } emitResult(); } void ContentJob::slotJobData( KIO::Job *, const QByteArray &data ) { m_data.append( data ); } diff --git a/src/aboutdialog/libattica-ocsclient/contentjob.h b/src/aboutdialog/libattica-ocsclient/contentjob.h index a9db7d74d2..23472aca03 100644 --- a/src/aboutdialog/libattica-ocsclient/contentjob.h +++ b/src/aboutdialog/libattica-ocsclient/contentjob.h @@ -1,63 +1,63 @@ /* This file is part of KDE. Copyright (c) 2008 Cornelius Schumacher 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef ATTICA_CONTENTJOB_H #define ATTICA_CONTENTJOB_H #include "content.h" #include -#include +#include namespace KIO { class Job; } namespace AmarokAttica { class ATTICA_EXPORT ContentJob : public KJob { Q_OBJECT public: ContentJob(); - void setUrl( const KUrl & ); + void setUrl( const QUrl & ); void start(); Content content() const; protected slots: void doWork(); void slotJobResult( KJob *job ); void slotJobData( KIO::Job *job, const QByteArray &data ); private: - KUrl m_url; + QUrl m_url; KIO::Job *m_job; QByteArray m_data; Content m_content; }; } #endif diff --git a/src/aboutdialog/libattica-ocsclient/contentlistjob.cpp b/src/aboutdialog/libattica-ocsclient/contentlistjob.cpp index 36e6bd65fd..fbd79c4e66 100644 --- a/src/aboutdialog/libattica-ocsclient/contentlistjob.cpp +++ b/src/aboutdialog/libattica-ocsclient/contentlistjob.cpp @@ -1,87 +1,87 @@ /* This file is part of KDE. Copyright (c) 2008 Cornelius Schumacher 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "contentlistjob.h" #include "contentparser.h" #include #include #include #include using namespace AmarokAttica; ContentListJob::ContentListJob() : m_job( 0 ) { } -void ContentListJob::setUrl( const KUrl &url ) +void ContentListJob::setUrl( const QUrl &url ) { m_url = url; } void ContentListJob::start() { QTimer::singleShot( 0, this, SLOT(doWork()) ); } Content::List ContentListJob::contentList() const { return m_contentList; } void ContentListJob::doWork() { qDebug() << m_url; m_job = KIO::get( m_url, KIO::NoReload, KIO::HideProgressInfo ); connect( m_job, SIGNAL(result(KJob*)), SLOT(slotJobResult(KJob*)) ); connect( m_job, SIGNAL(data(KIO::Job*,QByteArray)), SLOT(slotJobData(KIO::Job*,QByteArray)) ); } void ContentListJob::slotJobResult( KJob *job ) { m_job = 0; if ( job->error() ) { setError( job->error() ); setErrorText( job->errorText() ); emitResult(); } else { qDebug() << m_data; m_contentList = ContentParser().parseList( QString::fromUtf8( m_data.data() ) ); emitResult(); } } void ContentListJob::slotJobData( KIO::Job *, const QByteArray &data ) { m_data.append( data ); } diff --git a/src/aboutdialog/libattica-ocsclient/contentlistjob.h b/src/aboutdialog/libattica-ocsclient/contentlistjob.h index 600201fce4..94bbd07e85 100644 --- a/src/aboutdialog/libattica-ocsclient/contentlistjob.h +++ b/src/aboutdialog/libattica-ocsclient/contentlistjob.h @@ -1,63 +1,63 @@ /* This file is part of KDE. Copyright (c) 2008 Cornelius Schumacher 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef ATTICA_CONTENTLISTJOB_H #define ATTICA_CONTENTLISTJOB_H #include "content.h" -#include +#include #include namespace KIO { class Job; } namespace AmarokAttica { class ATTICA_EXPORT ContentListJob : public KJob { Q_OBJECT public: ContentListJob(); - void setUrl( const KUrl & ); + void setUrl( const QUrl & ); void start(); Content::List contentList() const; protected slots: void doWork(); void slotJobResult( KJob *job ); void slotJobData( KIO::Job *job, const QByteArray &data ); private: - KUrl m_url; + QUrl m_url; KIO::Job *m_job; QByteArray m_data; Content::List m_contentList; }; } #endif diff --git a/src/aboutdialog/libattica-ocsclient/eventjob.cpp b/src/aboutdialog/libattica-ocsclient/eventjob.cpp index d51e7bda90..54e05c88f8 100644 --- a/src/aboutdialog/libattica-ocsclient/eventjob.cpp +++ b/src/aboutdialog/libattica-ocsclient/eventjob.cpp @@ -1,91 +1,91 @@ /* This file is part of KDE. Copyright (c) 2009 Eckhart Wörner 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "eventjob.h" #include #include #include "eventparser.h" using namespace AmarokAttica; EventJob::EventJob() : m_job(0) { } -void EventJob::setUrl(const KUrl& url) +void EventJob::setUrl(const QUrl &url) { m_url = url; } void EventJob::start() { QTimer::singleShot(0, this, SLOT(doWork())); } Event EventJob::event() const { return m_event; } void EventJob::doWork() { m_job = KIO::get(m_url, KIO::NoReload, KIO::HideProgressInfo); connect(m_job, SIGNAL(result(KJob*)), SLOT(slotJobResult(KJob*))); connect(m_job, SIGNAL(data(KIO::Job*,QByteArray)), SLOT(slotJobData(KIO::Job*,QByteArray))); } void EventJob::slotJobResult(KJob* job) { m_job = 0; if (job->error()) { setError(job->error()); setErrorText(job->errorText()); emitResult(); } else { m_event = EventParser().parse(QString::fromUtf8(m_data.data())); emitResult(); } } void EventJob::slotJobData(KIO::Job* job, const QByteArray& data) { Q_UNUSED(job); m_data.append(data); } diff --git a/src/aboutdialog/libattica-ocsclient/eventjob.h b/src/aboutdialog/libattica-ocsclient/eventjob.h index d0c05b6d7f..f700b24acc 100644 --- a/src/aboutdialog/libattica-ocsclient/eventjob.h +++ b/src/aboutdialog/libattica-ocsclient/eventjob.h @@ -1,69 +1,69 @@ /* This file is part of KDE. Copyright (c) 2009 Eckhart Wörner 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef ATTICA_EVENTJOB_H #define ATTICA_EVENTJOB_H #include -#include +#include #include "atticaclient_export.h" #include "event.h" namespace KIO { class Job; } namespace AmarokAttica { class ATTICA_EXPORT EventJob : public KJob { Q_OBJECT public: EventJob(); - void setUrl(const KUrl& url); + void setUrl(const QUrl &url); void start(); Event event() const; using QObject::event; // Unhide QObject's event() protected slots: void doWork(); void slotJobResult(KJob* job); void slotJobData(KIO::Job* job, const QByteArray& data); private: - KUrl m_url; + QUrl m_url; KIO::Job* m_job; QByteArray m_data; Event m_event; }; } #endif diff --git a/src/aboutdialog/libattica-ocsclient/eventlistjob.cpp b/src/aboutdialog/libattica-ocsclient/eventlistjob.cpp index 916011c490..b983e45b66 100644 --- a/src/aboutdialog/libattica-ocsclient/eventlistjob.cpp +++ b/src/aboutdialog/libattica-ocsclient/eventlistjob.cpp @@ -1,91 +1,91 @@ /* This file is part of KDE. Copyright (c) 2009 Eckhart Wörner 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "eventlistjob.h" #include #include #include "eventparser.h" using namespace AmarokAttica; EventListJob::EventListJob() : m_job(0) { } -void EventListJob::setUrl(const KUrl& url) +void EventListJob::setUrl(const QUrl &url) { m_url = url; } void EventListJob::start() { QTimer::singleShot(0, this, SLOT(doWork())); } Event::List EventListJob::eventList() const { return m_eventList; } void EventListJob::doWork() { m_job = KIO::get(m_url, KIO::NoReload, KIO::HideProgressInfo); connect(m_job, SIGNAL(result(KJob*)), SLOT(slotJobResult(KJob*))); connect(m_job, SIGNAL(data(KIO::Job*,QByteArray)), SLOT(slotJobData(KIO::Job*,QByteArray))); } void EventListJob::slotJobResult(KJob* job) { m_job = 0; if (job->error()) { setError(job->error()); setErrorText(job->errorText()); emitResult(); } else { m_eventList = EventParser().parseList(QString::fromUtf8(m_data.data())); emitResult(); } } void EventListJob::slotJobData(KIO::Job* job, const QByteArray& data) { Q_UNUSED(job); m_data.append(data); } diff --git a/src/aboutdialog/libattica-ocsclient/eventlistjob.h b/src/aboutdialog/libattica-ocsclient/eventlistjob.h index 94157ff36d..02d206a93b 100644 --- a/src/aboutdialog/libattica-ocsclient/eventlistjob.h +++ b/src/aboutdialog/libattica-ocsclient/eventlistjob.h @@ -1,68 +1,68 @@ /* This file is part of KDE. Copyright (c) 2009 Eckhart Wörner 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef ATTICA_EVENTLISTJOB_H #define ATTICA_EVENTLISTJOB_H #include -#include +#include #include "atticaclient_export.h" #include "event.h" namespace KIO { class Job; } namespace AmarokAttica { class ATTICA_EXPORT EventListJob : public KJob { Q_OBJECT public: EventListJob(); - void setUrl(const KUrl& url); + void setUrl(const QUrl &url); void start(); Event::List eventList() const; protected slots: void doWork(); void slotJobResult(KJob* job); void slotJobData(KIO::Job* job, const QByteArray& data); private: - KUrl m_url; + QUrl m_url; KIO::Job* m_job; QByteArray m_data; Event::List m_eventList; }; } #endif diff --git a/src/aboutdialog/libattica-ocsclient/folderlistjob.cpp b/src/aboutdialog/libattica-ocsclient/folderlistjob.cpp index 06487a8f56..dc2c137c02 100644 --- a/src/aboutdialog/libattica-ocsclient/folderlistjob.cpp +++ b/src/aboutdialog/libattica-ocsclient/folderlistjob.cpp @@ -1,85 +1,85 @@ /* This file is part of KDE. Copyright (c) 2008 Cornelius Schumacher 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "folderlistjob.h" #include "folderparser.h" #include #include #include #include using namespace AmarokAttica; FolderListJob::FolderListJob() : m_job( 0 ) { } -void FolderListJob::setUrl( const KUrl &url ) +void FolderListJob::setUrl( const QUrl &url ) { m_url = url; } void FolderListJob::start() { QTimer::singleShot( 0, this, SLOT(doWork()) ); } Folder::List FolderListJob::folderList() const { return m_folderList; } void FolderListJob::doWork() { qDebug() << m_url; m_job = KIO::get( m_url, KIO::NoReload, KIO::HideProgressInfo ); connect( m_job, SIGNAL(result(KJob*)), SLOT(slotJobResult(KJob*)) ); connect( m_job, SIGNAL(data(KIO::Job*,QByteArray)), SLOT(slotJobData(KIO::Job*,QByteArray)) ); } void FolderListJob::slotJobResult( KJob *job ) { m_job = 0; if ( job->error() ) { setError( job->error() ); setErrorText( job->errorText() ); emitResult(); } else { qDebug() << m_data; m_folderList = FolderParser().parseList( QString::fromUtf8( m_data.data() ) ); emitResult(); } } void FolderListJob::slotJobData( KIO::Job *, const QByteArray &data ) { m_data.append( data ); } diff --git a/src/aboutdialog/libattica-ocsclient/folderlistjob.h b/src/aboutdialog/libattica-ocsclient/folderlistjob.h index bfdbf2eeaf..6714decc6b 100644 --- a/src/aboutdialog/libattica-ocsclient/folderlistjob.h +++ b/src/aboutdialog/libattica-ocsclient/folderlistjob.h @@ -1,63 +1,63 @@ /* This file is part of KDE. Copyright (c) 2008 Cornelius Schumacher 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef ATTICA_FOLDERLISTJOB_H #define ATTICA_FOLDERLISTJOB_H #include "folder.h" #include -#include +#include namespace KIO { class Job; } namespace AmarokAttica { class ATTICA_EXPORT FolderListJob : public KJob { Q_OBJECT public: FolderListJob(); - void setUrl( const KUrl & ); + void setUrl( const QUrl & ); void start(); Folder::List folderList() const; protected slots: void doWork(); void slotJobResult( KJob *job ); void slotJobData( KIO::Job *job, const QByteArray &data ); private: - KUrl m_url; + QUrl m_url; KIO::Job *m_job; QByteArray m_data; Folder::List m_folderList; }; } #endif diff --git a/src/aboutdialog/libattica-ocsclient/knowledgebase.cpp b/src/aboutdialog/libattica-ocsclient/knowledgebase.cpp index 0758507b15..ac4e7c3f81 100644 --- a/src/aboutdialog/libattica-ocsclient/knowledgebase.cpp +++ b/src/aboutdialog/libattica-ocsclient/knowledgebase.cpp @@ -1,156 +1,156 @@ /*************************************************************************** * This file is part of KDE. * * Copyright (C) 2009 Marco Martin * * * * 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., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ #include "knowledgebase.h" using namespace AmarokAttica; KnowledgeBase::KnowledgeBase() : m_contentId(0), m_comments(0) { } void KnowledgeBase::setId(QString id) { m_id = id; } QString KnowledgeBase::id() const { return m_id; } void KnowledgeBase::setContentId(int id) { m_contentId = id; } int KnowledgeBase::contentId() const { return m_contentId; } void KnowledgeBase::setUser(const QString &user) { m_user = user; } QString KnowledgeBase::user() const { return m_user; } void KnowledgeBase::setStatus(const QString status) { m_status = status; } QString KnowledgeBase::status() const { return m_status; } void KnowledgeBase::setChanged(const QDateTime &changed) { m_changed = changed; } QDateTime KnowledgeBase::changed() const { return m_changed; } void KnowledgeBase::setName(const QString &name) { m_name = name; } QString KnowledgeBase::name() const { return m_name; } void KnowledgeBase::setDescription(const QString &description) { m_description = description; } QString KnowledgeBase::description() const { return m_description; } void KnowledgeBase::setAnswer(const QString &answer) { m_answer = answer; } QString KnowledgeBase::answer() const { return m_answer; } void KnowledgeBase::setComments(int comments) { m_comments = comments; } int KnowledgeBase::comments() const { return m_comments; } -void KnowledgeBase::setDetailPage(const KUrl &detailPage) +void KnowledgeBase::setDetailPage(const QUrl &detailPage) { m_detailPage = detailPage; } -KUrl KnowledgeBase::detailPage() const +QUrl KnowledgeBase::detailPage() const { return m_detailPage; } void KnowledgeBase::addExtendedAttribute( const QString &key, const QString &value ) { m_extendedAttributes.insert( key, value ); } QString KnowledgeBase::extendedAttribute( const QString &key ) const { return m_extendedAttributes.value( key ); } QMap KnowledgeBase::extendedAttributes() const { return m_extendedAttributes; } diff --git a/src/aboutdialog/libattica-ocsclient/knowledgebase.h b/src/aboutdialog/libattica-ocsclient/knowledgebase.h index a6afb5645f..3818e152b3 100644 --- a/src/aboutdialog/libattica-ocsclient/knowledgebase.h +++ b/src/aboutdialog/libattica-ocsclient/knowledgebase.h @@ -1,99 +1,99 @@ /*************************************************************************** * This file is part of KDE. * * Copyright (C) 2009 Marco Martin * * * * 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., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ #ifndef ATTICA_KNOWLEDGEBASE_H #define ATTICA_KNOWLEDGEBASE_H #include "atticaclient_export.h" #include -#include +#include namespace AmarokAttica { class ATTICA_EXPORT KnowledgeBase { public: typedef QList List; struct Metadata { QString status; QString message; int totalItems; int itemsPerPage; }; KnowledgeBase(); void setId(QString id); QString id() const; void setContentId(int id); int contentId() const; void setUser(const QString &user); QString user() const; void setStatus(const QString status); QString status() const; void setChanged(const QDateTime &changed); QDateTime changed() const; void setName(const QString &name); QString name() const; void setDescription(const QString &description); QString description() const; void setAnswer(const QString &answer); QString answer() const; void setComments(int comments); int comments() const; - void setDetailPage(const KUrl &detailPage); - KUrl detailPage() const; + void setDetailPage(const QUrl &detailPage); + QUrl detailPage() const; void addExtendedAttribute( const QString &key, const QString &value ); QString extendedAttribute( const QString &key ) const; QMap extendedAttributes() const; private: QString m_id; int m_contentId; QString m_user; QString m_status; QDateTime m_changed; QString m_name; QString m_description; QString m_answer; int m_comments; - KUrl m_detailPage; + QUrl m_detailPage; QMap m_extendedAttributes; }; } #endif diff --git a/src/aboutdialog/libattica-ocsclient/knowledgebasejob.cpp b/src/aboutdialog/libattica-ocsclient/knowledgebasejob.cpp index 0d7460b0f2..f678b56c08 100644 --- a/src/aboutdialog/libattica-ocsclient/knowledgebasejob.cpp +++ b/src/aboutdialog/libattica-ocsclient/knowledgebasejob.cpp @@ -1,91 +1,91 @@ /* This file is part of KDE. Copyright (c) 2008 Cornelius Schumacher Copyright (c) 2009 Marco Martin 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "knowledgebasejob.h" #include "knowledgebaseparser.h" #include #include #include #include using namespace AmarokAttica; KnowledgeBaseJob::KnowledgeBaseJob() : m_job( 0 ) { } -void KnowledgeBaseJob::setUrl( const KUrl &url ) +void KnowledgeBaseJob::setUrl( const QUrl &url ) { m_url = url; } void KnowledgeBaseJob::start() { QTimer::singleShot( 0, this, SLOT(doWork()) ); } KnowledgeBase KnowledgeBaseJob::knowledgeBase() const { return m_knowledgeBase; } KnowledgeBase::Metadata KnowledgeBaseJob::metadata() const { return m_metadata; } void KnowledgeBaseJob::doWork() { qDebug() << m_url; m_job = KIO::get( m_url, KIO::NoReload, KIO::HideProgressInfo ); connect( m_job, SIGNAL(result(KJob*)), SLOT(slotJobResult(KJob*)) ); connect( m_job, SIGNAL(data(KIO::Job*,QByteArray)), SLOT(slotJobData(KIO::Job*,QByteArray)) ); } void KnowledgeBaseJob::slotJobResult( KJob *job ) { m_job = 0; if ( job->error() ) { setError( job->error() ); setErrorText( job->errorText() ); } else { qDebug() << m_data; KnowledgeBaseParser parser; m_knowledgeBase = parser.parse( QString::fromUtf8( m_data.data() ) ); m_metadata = parser.lastMetadata(); } emitResult(); } void KnowledgeBaseJob::slotJobData( KIO::Job *, const QByteArray &data ) { m_data.append( data ); } diff --git a/src/aboutdialog/libattica-ocsclient/knowledgebasejob.h b/src/aboutdialog/libattica-ocsclient/knowledgebasejob.h index dfa1194150..fbb2a82d22 100644 --- a/src/aboutdialog/libattica-ocsclient/knowledgebasejob.h +++ b/src/aboutdialog/libattica-ocsclient/knowledgebasejob.h @@ -1,65 +1,65 @@ /* This file is part of KDE. Copyright (c) 2008 Cornelius Schumacher Copyright (c) 2009 Marco Martin 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef ATTICA_KNOWLEDGEBASEJOB_H #define ATTICA_KNOWLEDGEBASEJOB_H #include "knowledgebase.h" #include namespace KIO { class Job; } namespace AmarokAttica { class ATTICA_EXPORT KnowledgeBaseJob : public KJob { Q_OBJECT public: KnowledgeBaseJob(); - void setUrl( const KUrl & ); + void setUrl( const QUrl & ); void start(); KnowledgeBase knowledgeBase() const; KnowledgeBase::Metadata metadata() const; protected slots: void doWork(); void slotJobResult( KJob *job ); void slotJobData( KIO::Job *job, const QByteArray &data ); private: - KUrl m_url; + QUrl m_url; KIO::Job *m_job; QByteArray m_data; KnowledgeBase m_knowledgeBase; KnowledgeBase::Metadata m_metadata; }; } #endif diff --git a/src/aboutdialog/libattica-ocsclient/knowledgebaselistjob.cpp b/src/aboutdialog/libattica-ocsclient/knowledgebaselistjob.cpp index c685a71486..a7d6b955d6 100644 --- a/src/aboutdialog/libattica-ocsclient/knowledgebaselistjob.cpp +++ b/src/aboutdialog/libattica-ocsclient/knowledgebaselistjob.cpp @@ -1,93 +1,93 @@ /* This file is part of KDE. Copyright (c) 2008 Cornelius Schumacher Copyright (c) 2009 Marco Martin 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "knowledgebaselistjob.h" #include "knowledgebaseparser.h" #include #include #include #include using namespace AmarokAttica; KnowledgeBaseListJob::KnowledgeBaseListJob() : m_job( 0 ) { } -void KnowledgeBaseListJob::setUrl( const KUrl &url ) +void KnowledgeBaseListJob::setUrl( const QUrl &url ) { m_url = url; } void KnowledgeBaseListJob::start() { QTimer::singleShot( 0, this, SLOT(doWork()) ); } KnowledgeBase::List KnowledgeBaseListJob::knowledgeBaseList() const { return m_knowledgeBaseList; } KnowledgeBase::Metadata KnowledgeBaseListJob::metadata() const { return m_metadata; } void KnowledgeBaseListJob::doWork() { qDebug() << m_url; m_job = KIO::get( m_url, KIO::NoReload, KIO::HideProgressInfo ); connect( m_job, SIGNAL(result(KJob*)), SLOT(slotJobResult(KJob*)) ); connect( m_job, SIGNAL(data(KIO::Job*,QByteArray)), SLOT(slotJobData(KIO::Job*,QByteArray)) ); } void KnowledgeBaseListJob::slotJobResult( KJob *job ) { m_job = 0; if ( job->error() ) { setError( job->error() ); setErrorText( job->errorText() ); emitResult(); } else { qDebug() << m_data; KnowledgeBaseParser parser; m_knowledgeBaseList = parser.parseList( QString::fromUtf8( m_data.data() ) ); m_metadata = parser.lastMetadata(); emitResult(); } } void KnowledgeBaseListJob::slotJobData( KIO::Job *, const QByteArray &data ) { m_data.append( data ); } diff --git a/src/aboutdialog/libattica-ocsclient/knowledgebaselistjob.h b/src/aboutdialog/libattica-ocsclient/knowledgebaselistjob.h index 49125a9c99..9377920820 100644 --- a/src/aboutdialog/libattica-ocsclient/knowledgebaselistjob.h +++ b/src/aboutdialog/libattica-ocsclient/knowledgebaselistjob.h @@ -1,66 +1,66 @@ /* This file is part of KDE. Copyright (c) 2008 Cornelius Schumacher Copyright (c) 2009 Marco Martin 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef ATTICA_KNOWLEDGEBASELISTJOB_H #define ATTICA_KNOWLEDGEBASELISTJOB_H #include "knowledgebase.h" -#include +#include #include namespace KIO { class Job; } namespace AmarokAttica { class ATTICA_EXPORT KnowledgeBaseListJob : public KJob { Q_OBJECT public: KnowledgeBaseListJob(); - void setUrl( const KUrl & ); + void setUrl( const QUrl & ); void start(); KnowledgeBase::List knowledgeBaseList() const; KnowledgeBase::Metadata metadata() const; protected slots: void doWork(); void slotJobResult( KJob *job ); void slotJobData( KIO::Job *job, const QByteArray &data ); private: - KUrl m_url; + QUrl m_url; KIO::Job *m_job; QByteArray m_data; KnowledgeBase::List m_knowledgeBaseList; KnowledgeBase::Metadata m_metadata; }; } #endif diff --git a/src/aboutdialog/libattica-ocsclient/knowledgebaseparser.cpp b/src/aboutdialog/libattica-ocsclient/knowledgebaseparser.cpp index 1c92fa05a6..22d202f510 100644 --- a/src/aboutdialog/libattica-ocsclient/knowledgebaseparser.cpp +++ b/src/aboutdialog/libattica-ocsclient/knowledgebaseparser.cpp @@ -1,152 +1,152 @@ /* This file is part of KDE. Copyright (c) 2008 Cornelius Schumacher Copyright (c) 2009 Marco Martin 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "knowledgebaseparser.h" #include using namespace AmarokAttica; KnowledgeBaseParser::KnowledgeBaseParser() { } KnowledgeBase::List KnowledgeBaseParser::parseList( const QString &xmlString ) { KnowledgeBase::List KnowledgeBaseList; QXmlStreamReader xml( xmlString ); m_lastMetadata = parseMetadata(xml); while ( !xml.atEnd() ) { xml.readNext(); if ( xml.isStartElement() && xml.name() == "content" ) { KnowledgeBase KnowledgeBase = parseKnowledgeBase( xml ); KnowledgeBaseList.append( KnowledgeBase ); } } return KnowledgeBaseList; } KnowledgeBase KnowledgeBaseParser::parse( const QString &xmlString ) { KnowledgeBase knowledgeBase; QXmlStreamReader xml( xmlString ); m_lastMetadata = parseMetadata(xml); while ( !xml.atEnd() ) { xml.readNext(); if ( xml.isStartElement() && xml.name() == "knowledgebase" ) { knowledgeBase = parseKnowledgeBase( xml ); } } return knowledgeBase; } KnowledgeBase::Metadata KnowledgeBaseParser::lastMetadata() { return m_lastMetadata; } KnowledgeBase::Metadata KnowledgeBaseParser::parseMetadata( QXmlStreamReader &xml ) { KnowledgeBase::Metadata meta; meta.status.clear(); meta.message.clear(); meta.totalItems = 0; meta.itemsPerPage = 0; while ( !xml.atEnd() ) { xml.readNext(); if (xml.isStartElement() && xml.name() == "meta") { while ( !xml.atEnd() ) { xml.readNext(); if (xml.isEndElement() && xml.name() == "meta") { break; } else if (xml.isStartElement()) { if (xml.name() == "status") { meta.status = xml.readElementText(); } else if (xml.name() == "message") { meta.message = xml.readElementText(); } else if (xml.name() == "totalitems") { meta.totalItems = xml.readElementText().toInt(); } else if (xml.name() == "itemsperpage") { meta.itemsPerPage = xml.readElementText().toInt(); } } } break; } } return meta; } KnowledgeBase KnowledgeBaseParser::parseKnowledgeBase( QXmlStreamReader &xml ) { KnowledgeBase knowledgeBase; while ( !xml.atEnd() ) { xml.readNext(); if ( xml.isStartElement() ) { if ( xml.name() == "id" ) { knowledgeBase.setId( xml.readElementText() ); } else if ( xml.name() == "status" ) { knowledgeBase.setStatus( xml.readElementText() ); } else if ( xml.name() == "contentId" ) { knowledgeBase.setContentId( xml.readElementText().toInt() ); } else if ( xml.name() == "user" ) { knowledgeBase.setUser( xml.readElementText() ); } else if ( xml.name() == "changed" ) { knowledgeBase.setChanged( QDateTime::fromString( xml.readElementText(), Qt::ISODate ) ); } else if ( xml.name() == "description" ) { knowledgeBase.setDescription( xml.readElementText() ); } else if ( xml.name() == "answer" ) { knowledgeBase.setAnswer( xml.readElementText() ); } else if ( xml.name() == "comments" ) { knowledgeBase.setComments( xml.readElementText().toInt() ); } else if ( xml.name() == "detailpage" ) { - knowledgeBase.setDetailPage( KUrl(xml.readElementText()) ); + knowledgeBase.setDetailPage( QUrl(xml.readElementText()) ); } else if ( xml.name() == "contentid" ) { knowledgeBase.setContentId( xml.readElementText().toInt() ); } else if ( xml.name() == "name" ) { knowledgeBase.setName( xml.readElementText() ); } else { knowledgeBase.addExtendedAttribute( xml.name().toString(), xml.readElementText() ); } } if ( xml.isEndElement() && xml.name() == "content" ) break; } return knowledgeBase; } diff --git a/src/aboutdialog/libattica-ocsclient/messagelistjob.cpp b/src/aboutdialog/libattica-ocsclient/messagelistjob.cpp index 6eeddc52f7..5809bd4356 100644 --- a/src/aboutdialog/libattica-ocsclient/messagelistjob.cpp +++ b/src/aboutdialog/libattica-ocsclient/messagelistjob.cpp @@ -1,87 +1,87 @@ /* This file is part of KDE. Copyright (c) 2008 Cornelius Schumacher 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "messagelistjob.h" #include "messageparser.h" #include #include #include #include using namespace AmarokAttica; MessageListJob::MessageListJob() : m_job( 0 ) { } -void MessageListJob::setUrl( const KUrl &url ) +void MessageListJob::setUrl( const QUrl &url ) { m_url = url; } void MessageListJob::start() { QTimer::singleShot( 0, this, SLOT(doWork()) ); } Message::List MessageListJob::messageList() const { return m_messageList; } void MessageListJob::doWork() { qDebug() << m_url; m_job = KIO::get( m_url, KIO::NoReload, KIO::HideProgressInfo ); connect( m_job, SIGNAL(result(KJob*)), SLOT(slotJobResult(KJob*)) ); connect( m_job, SIGNAL(data(KIO::Job*,QByteArray)), SLOT(slotJobData(KIO::Job*,QByteArray)) ); } void MessageListJob::slotJobResult( KJob *job ) { m_job = 0; if ( job->error() ) { setError( job->error() ); setErrorText( job->errorText() ); emitResult(); } else { qDebug() << m_data; m_messageList = MessageParser().parseList( QString::fromUtf8( m_data.data() ) ); emitResult(); } } void MessageListJob::slotJobData( KIO::Job *, const QByteArray &data ) { m_data.append( data ); } diff --git a/src/aboutdialog/libattica-ocsclient/messagelistjob.h b/src/aboutdialog/libattica-ocsclient/messagelistjob.h index de006900d6..75117167dc 100644 --- a/src/aboutdialog/libattica-ocsclient/messagelistjob.h +++ b/src/aboutdialog/libattica-ocsclient/messagelistjob.h @@ -1,63 +1,63 @@ /* This file is part of KDE. Copyright (c) 2008 Cornelius Schumacher 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef ATTICA_MESSAGELISTJOB_H #define ATTICA_MESSAGELISTJOB_H #include "message.h" #include -#include +#include namespace KIO { class Job; } namespace AmarokAttica { class ATTICA_EXPORT MessageListJob : public KJob { Q_OBJECT public: MessageListJob(); - void setUrl( const KUrl & ); + void setUrl( const QUrl & ); void start(); Message::List messageList() const; protected slots: void doWork(); void slotJobResult( KJob *job ); void slotJobData( KIO::Job *job, const QByteArray &data ); private: - KUrl m_url; + QUrl m_url; KIO::Job *m_job; QByteArray m_data; Message::List m_messageList; }; } #endif diff --git a/src/aboutdialog/libattica-ocsclient/person.cpp b/src/aboutdialog/libattica-ocsclient/person.cpp index 286340e4ce..ae1388ce34 100644 --- a/src/aboutdialog/libattica-ocsclient/person.cpp +++ b/src/aboutdialog/libattica-ocsclient/person.cpp @@ -1,154 +1,154 @@ /* This file is part of KDE. Copyright (c) 2008 Cornelius Schumacher 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "person.h" using namespace AmarokAttica; Person::Person() : m_latitude( 0 ), m_longitude( 0 ) { } void Person::setId( const QString &u ) { m_id = u; } QString Person::id() const { return m_id; } void Person::setFirstName( const QString &name ) { m_firstName = name; } QString Person::firstName() const { return m_firstName; } void Person::setLastName( const QString &name ) { m_lastName = name; } QString Person::lastName() const { return m_lastName; } void Person::setBirthday( const QDate &d ) { m_birthday = d; } QDate Person::birthday() const { return m_birthday; } void Person::setCountry( const QString &c ) { m_country = c; } QString Person::country() const { return m_country; } void Person::setLatitude( qreal l ) { m_latitude = l; } qreal Person::latitude() const { return m_latitude; } void Person::setLongitude( qreal l ) { m_longitude = l; } qreal Person::longitude() const { return m_longitude; } -void Person::setAvatarUrl( const KUrl &url ) +void Person::setAvatarUrl( const QUrl &url ) { m_avatarUrl = url; } -KUrl Person::avatarUrl() const +QUrl Person::avatarUrl() const { return m_avatarUrl; } void Person::setAvatar( const QPixmap &p ) { m_avatar = p; } QPixmap Person::avatar() const { return m_avatar; } void Person::setHomepage( const QString &h ) { m_homepage = h; } QString Person::homepage() const { return m_homepage; } void Person::setCity( const QString &h ) { m_city = h; } QString Person::city() const { return m_city; } void Person::addExtendedAttribute( const QString &key, const QString &value ) { m_extendedAttributes.insert( key, value ); } QString Person::extendedAttribute( const QString &key ) const { return m_extendedAttributes.value( key ); } QMap Person::extendedAttributes() const { return m_extendedAttributes; } diff --git a/src/aboutdialog/libattica-ocsclient/person.h b/src/aboutdialog/libattica-ocsclient/person.h index 81b2c822b9..0ebd9b6c9e 100644 --- a/src/aboutdialog/libattica-ocsclient/person.h +++ b/src/aboutdialog/libattica-ocsclient/person.h @@ -1,98 +1,98 @@ /* This file is part of KDE. Copyright (c) 2008 Cornelius Schumacher 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef ATTICA_PERSON_H #define ATTICA_PERSON_H #include #include #include -#include +#include #include "atticaclient_export.h" namespace AmarokAttica { class ATTICA_EXPORT Person { public: typedef QList List; Person(); void setId( const QString & ); QString id() const; void setFirstName( const QString & ); QString firstName() const; void setLastName( const QString & ); QString lastName() const; void setBirthday( const QDate & ); QDate birthday() const; void setCountry( const QString & ); QString country() const; void setLatitude( qreal ); qreal latitude() const; void setLongitude( qreal ); qreal longitude() const; - void setAvatarUrl( const KUrl & ); - KUrl avatarUrl() const; + void setAvatarUrl( const QUrl & ); + QUrl avatarUrl() const; void setAvatar( const QPixmap & ); QPixmap avatar() const; void setHomepage( const QString & ); QString homepage() const; void setCity( const QString & ); QString city() const; void addExtendedAttribute( const QString &key, const QString &value ); QString extendedAttribute( const QString &key ) const; QMap extendedAttributes() const; private: QString m_id; QString m_firstName; QString m_lastName; QDate m_birthday; QString m_country; qreal m_latitude; qreal m_longitude; - KUrl m_avatarUrl; + QUrl m_avatarUrl; QPixmap m_avatar; QString m_homepage; QString m_city; QMap m_extendedAttributes; }; } #endif diff --git a/src/aboutdialog/libattica-ocsclient/personjob.cpp b/src/aboutdialog/libattica-ocsclient/personjob.cpp index 6cc755c053..f58aab5269 100644 --- a/src/aboutdialog/libattica-ocsclient/personjob.cpp +++ b/src/aboutdialog/libattica-ocsclient/personjob.cpp @@ -1,118 +1,118 @@ /* This file is part of KDE. Copyright (c) 2008 Cornelius Schumacher 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "personjob.h" #include "personparser.h" #include #include #include #include using namespace AmarokAttica; PersonJob::PersonJob() : m_job( 0 ) { } -void PersonJob::setUrl( const KUrl &url ) +void PersonJob::setUrl( const QUrl &url ) { m_url = url; } void PersonJob::start() { QTimer::singleShot( 0, this, SLOT(doWork()) ); } Person PersonJob::person() const { return m_person; } void PersonJob::doWork() { qDebug() << m_url; m_job = KIO::get( m_url, KIO::NoReload, KIO::HideProgressInfo ); connect( m_job, SIGNAL(result(KJob*)), SLOT(slotUserJobResult(KJob*)) ); connect( m_job, SIGNAL(data(KIO::Job*,QByteArray)), SLOT(slotUserJobData(KIO::Job*,QByteArray)) ); } void PersonJob::slotUserJobResult( KJob *job ) { m_job = 0; if ( job->error() ) { setError( job->error() ); setErrorText( job->errorText() ); emitResult(); } else { // qDebug() << m_userData; m_person = PersonParser().parse( m_userData ); if (!m_person.avatarUrl().isEmpty()) { qDebug() << "Getting avatar from" << m_person.avatarUrl(); m_job = KIO::get( m_person.avatarUrl(), KIO::NoReload, KIO::HideProgressInfo ); connect( m_job, SIGNAL(result(KJob*)), SLOT(slotAvatarJobResult(KJob*)) ); connect( m_job, SIGNAL(data(KIO::Job*,QByteArray)), SLOT(slotAvatarJobData(KIO::Job*,QByteArray)) ); } else { emitResult(); } } } void PersonJob::slotUserJobData( KIO::Job *, const QByteArray &data ) { m_userData.append( QString::fromUtf8( data.data(), data.size() + 1 ) ); } void PersonJob::slotAvatarJobResult( KJob *job ) { m_job = 0; if ( job->error() ) { qWarning() << "Error retrieving Avatar:" << job->errorText(); } else { QPixmap pic; if ( pic.loadFromData( m_avatarData ) ) { m_person.setAvatar( pic ); } } emitResult(); } void PersonJob::slotAvatarJobData( KIO::Job *, const QByteArray &data ) { m_avatarData.append( data ); } diff --git a/src/aboutdialog/libattica-ocsclient/personjob.h b/src/aboutdialog/libattica-ocsclient/personjob.h index ce4091ec1f..bb1c56cbda 100644 --- a/src/aboutdialog/libattica-ocsclient/personjob.h +++ b/src/aboutdialog/libattica-ocsclient/personjob.h @@ -1,66 +1,66 @@ /* This file is part of KDE. Copyright (c) 2008 Cornelius Schumacher 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef ATTICA_PERSONJOB_H #define ATTICA_PERSONJOB_H #include "person.h" #include namespace KIO { class Job; } namespace AmarokAttica { class ATTICA_EXPORT PersonJob : public KJob { Q_OBJECT public: PersonJob(); - void setUrl( const KUrl & ); + void setUrl( const QUrl & ); void start(); Person person() const; protected slots: void doWork(); void slotUserJobResult( KJob *job ); void slotUserJobData( KIO::Job *job, const QByteArray &data ); void slotAvatarJobResult( KJob *job ); void slotAvatarJobData( KIO::Job *job, const QByteArray &data ); private: - KUrl m_url; + QUrl m_url; KIO::Job *m_job; QString m_userData; QByteArray m_avatarData; Person m_person; }; } #endif diff --git a/src/aboutdialog/libattica-ocsclient/personlistjob.cpp b/src/aboutdialog/libattica-ocsclient/personlistjob.cpp index 1637f7e1c4..47d7c53d89 100644 --- a/src/aboutdialog/libattica-ocsclient/personlistjob.cpp +++ b/src/aboutdialog/libattica-ocsclient/personlistjob.cpp @@ -1,120 +1,120 @@ /* This file is part of KDE. Copyright (c) 2008 Cornelius Schumacher 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "personlistjob.h" #include "personparser.h" #include #include #include #include using namespace AmarokAttica; PersonListJob::PersonListJob() : m_job( 0 ) { } -void PersonListJob::setUrl( const KUrl &url ) +void PersonListJob::setUrl( const QUrl &url ) { m_url = url; } void PersonListJob::start() { QTimer::singleShot( 0, this, SLOT(doWork()) ); } Person::List PersonListJob::personList() const { return m_personList; } void PersonListJob::doWork() { qDebug() << m_url; m_job = KIO::get( m_url, KIO::NoReload, KIO::HideProgressInfo ); connect( m_job, SIGNAL(result(KJob*)), SLOT(slotUserJobResult(KJob*)) ); connect( m_job, SIGNAL(data(KIO::Job*,QByteArray)), SLOT(slotUserJobData(KIO::Job*,QByteArray)) ); } void PersonListJob::slotUserJobResult( KJob *job ) { m_job = 0; if ( job->error() ) { setError( job->error() ); setErrorText( job->errorText() ); emitResult(); } else { // qDebug() << m_userData; m_personList = PersonParser().parseList( m_userData ); #if 0 m_job = KIO::get( m_person.avatarUrl(), KIO::NoReload, KIO::HideProgressInfo ); connect( m_job, SIGNAL(result(KJob*)), SLOT(slotAvatarJobResult(KJob*)) ); connect( m_job, SIGNAL(data(KIO::Job*,QByteArray)), SLOT(slotAvatarJobData(KIO::Job*,QByteArray)) ); #else emitResult(); #endif } } void PersonListJob::slotUserJobData( KIO::Job *, const QByteArray &data ) { m_userData.append( QString::fromUtf8( data.data(), data.size() + 1 ) ); } void PersonListJob::slotAvatarJobResult( KJob *job ) { m_job = 0; if ( job->error() ) { setError( job->error() ); setErrorText( job->errorText() ); } else { QPixmap pic; if ( !pic.loadFromData( m_avatarData ) ) { setError( UserDefinedError ); setErrorText( i18n("Unable to parse avatar image data.") ); } else { // m_person.setAvatar( pic ); } } emitResult(); } void PersonListJob::slotAvatarJobData( KIO::Job *, const QByteArray &data ) { m_avatarData.append( data ); } diff --git a/src/aboutdialog/libattica-ocsclient/personlistjob.h b/src/aboutdialog/libattica-ocsclient/personlistjob.h index ab42a87117..8a74053aad 100644 --- a/src/aboutdialog/libattica-ocsclient/personlistjob.h +++ b/src/aboutdialog/libattica-ocsclient/personlistjob.h @@ -1,66 +1,66 @@ /* This file is part of KDE. Copyright (c) 2008 Cornelius Schumacher 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef ATTICA_PERSONLISTJOB_H #define ATTICA_PERSONLISTJOB_H #include "person.h" #include namespace KIO { class Job; } namespace AmarokAttica { class ATTICA_EXPORT PersonListJob : public KJob { Q_OBJECT public: PersonListJob(); - void setUrl( const KUrl & ); + void setUrl( const QUrl & ); void start(); Person::List personList() const; protected slots: void doWork(); void slotUserJobResult( KJob *job ); void slotUserJobData( KIO::Job *job, const QByteArray &data ); void slotAvatarJobResult( KJob *job ); void slotAvatarJobData( KIO::Job *job, const QByteArray &data ); private: - KUrl m_url; + QUrl m_url; KIO::Job *m_job; QString m_userData; QByteArray m_avatarData; Person::List m_personList; }; } #endif diff --git a/src/aboutdialog/libattica-ocsclient/postjob.cpp b/src/aboutdialog/libattica-ocsclient/postjob.cpp index 7b52311c22..cce72a32ec 100644 --- a/src/aboutdialog/libattica-ocsclient/postjob.cpp +++ b/src/aboutdialog/libattica-ocsclient/postjob.cpp @@ -1,128 +1,128 @@ /* This file is part of KDE. Copyright (c) 2008 Cornelius Schumacher 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "postjob.h" #include #include #include #include #include using namespace AmarokAttica; PostJob::PostJob() : m_job( 0 ) { } -void PostJob::setUrl( const KUrl &url ) +void PostJob::setUrl( const QUrl &url ) { m_url = url; } void PostJob::setData( const QString &name, const QString &value ) { m_data.insert( name, value ); } void PostJob::start() { QTimer::singleShot( 0, this, SLOT(doWork()) ); } QString PostJob::status() const { return m_status; } QString PostJob::statusMessage() const { return m_statusMessage; } void PostJob::doWork() { QString postData; const QStringList dataKeys = m_data.keys(); foreach( const QString &name, dataKeys ) { m_url.addQueryItem( name, m_data.value( name ) ); } qDebug() << m_url; m_job = KIO::http_post( m_url, postData.toUtf8(), KIO::HideProgressInfo ); connect( m_job, SIGNAL(result(KJob*)), SLOT(slotJobResult(KJob*)) ); connect( m_job, SIGNAL(data(KIO::Job*,QByteArray)), SLOT(slotJobData(KIO::Job*,QByteArray)) ); } void PostJob::slotJobResult( KJob *job ) { m_job = 0; qDebug() << "RESPONSE" << m_responseData; if ( job->error() ) { setError( job->error() ); setErrorText( job->errorText() ); } else { qDebug() << "No error "; QXmlStreamReader xml( m_responseData ); while ( !xml.atEnd() ) { xml.readNext(); if ( xml.isStartElement() && xml.name() == "meta" ) { while ( !xml.atEnd() ) { xml.readNext(); if ( xml.isStartElement() ) { if ( xml.name() == "status" ) { m_status = xml.readElementText(); } else if ( xml.name() == "message" ) { m_statusMessage = xml.readElementText(); } } if ( xml.isEndElement() && xml.name() == "meta" ) break; } } } qDebug() << "STATUS:" << m_status; if ( m_status != "ok" ) { setError( KJob::UserDefinedError ); setErrorText( m_status + ": " + m_statusMessage ); } } emitResult(); } void PostJob::slotJobData( KIO::Job *, const QByteArray &data ) { m_responseData.append( QString::fromUtf8( data.data(), data.size() + 1 ) ); } diff --git a/src/aboutdialog/libattica-ocsclient/postjob.h b/src/aboutdialog/libattica-ocsclient/postjob.h index 9fc30f4e7d..0e98446b56 100644 --- a/src/aboutdialog/libattica-ocsclient/postjob.h +++ b/src/aboutdialog/libattica-ocsclient/postjob.h @@ -1,67 +1,67 @@ /* This file is part of KDE. Copyright (c) 2008 Cornelius Schumacher 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef ATTICA_POSTJOB_H #define ATTICA_POSTJOB_H #include "atticaclient_export.h" #include -#include +#include namespace KIO { class Job; } namespace AmarokAttica { class ATTICA_EXPORT PostJob : public KJob { Q_OBJECT public: PostJob(); - void setUrl( const KUrl & ); + void setUrl( const QUrl & ); void setData( const QString &name, const QString &value ); void start(); QString status() const; QString statusMessage() const; protected slots: void doWork(); void slotJobResult( KJob *job ); void slotJobData( KIO::Job *, const QByteArray & ); private: - KUrl m_url; + QUrl m_url; QMap m_data; KIO::Job *m_job; QString m_responseData; QString m_status; QString m_statusMessage; }; } #endif diff --git a/src/aboutdialog/libattica-ocsclient/provider.cpp b/src/aboutdialog/libattica-ocsclient/provider.cpp index a57fb5a503..a89f613a7e 100644 --- a/src/aboutdialog/libattica-ocsclient/provider.cpp +++ b/src/aboutdialog/libattica-ocsclient/provider.cpp @@ -1,437 +1,438 @@ /* This file is part of KDE. Copyright (c) 2008 Cornelius Schumacher 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "provider.h" #include -#include +#include #include "activitylistjob.h" #include "categorylistjob.h" #include "contentjob.h" #include "contentlistjob.h" #include "eventjob.h" #include "eventlistjob.h" #include "folderlistjob.h" #include "knowledgebasejob.h" #include "knowledgebaselistjob.h" #include "messagelistjob.h" #include "personjob.h" #include "personlistjob.h" #include "postjob.h" #include "providerinitjob.h" using namespace AmarokAttica; class Provider::Private : public QSharedData { public: - KUrl m_baseUrl; + QUrl m_baseUrl; QString m_id; QString m_name; Private(const Private& other) : QSharedData(other), m_baseUrl(other.m_baseUrl), m_id(other.m_id), m_name(other.m_name) { } - Private(const QString& id, const KUrl& baseUrl, const QString name) + Private(const QString& id, const QUrl &baseUrl, const QString name) : m_baseUrl(baseUrl), m_id(id), m_name(name) { } }; ProviderInitJob* Provider::byId(const QString& id) { ProviderInitJob* job = new ProviderInitJob(id); job->start(); return job; } Provider::Provider() - : d(new Private(QString(), KUrl(), QString())) + : d(new Private(QString(), QUrl(), QString())) { } Provider::Provider(const Provider& other) : d(other.d) { } -Provider::Provider(const QString& id, const KUrl& baseUrl, const QString& name) +Provider::Provider(const QString& id, const QUrl &baseUrl, const QString& name) : d(new Private(id, baseUrl, name)) { } Provider& Provider::operator=(const AmarokAttica::Provider & other) { d = other.d; return *this; } Provider::~Provider() { } QString Provider::id() const { return d->m_id; } QString Provider::name() const { return d->m_name; } PersonJob* Provider::requestPerson(const QString& id) const { - KUrl url = createUrl( "person/data/" + id ); + QUrl url = createUrl( "person/data/" + id ); return doRequestPerson( url ); } PersonJob* Provider::requestPersonSelf() { - KUrl url = createUrl( "person/self" ); + QUrl url = createUrl( "person/self" ); return doRequestPerson( url ); } PersonListJob* Provider::requestPersonSearchByName(const QString& name) { - KUrl url = createUrl( "person/data"); + QUrl url = createUrl( "person/data"); url.addQueryItem("name", name); return doRequestPersonList( url ); } PersonListJob* Provider::requestPersonSearchByLocation(qreal latitude, qreal longitude, qreal distance, int page, int pageSize) { - KUrl url = createUrl( "person/data" ); + QUrl url = createUrl( "person/data" ); url.addQueryItem("latitude", QString::number(latitude)); url.addQueryItem("longitude", QString::number(longitude)); url.addQueryItem("distance", QString::number(distance)); url.addQueryItem("page", QString::number(page)); url.addQueryItem("pagesize", QString::number(pageSize)); qDebug() << "Location-based search:" << latitude << longitude << distance; qDebug() << "URL:" << url; return doRequestPersonList( url ); } PersonListJob* Provider::requestFriend(const QString& id, int page, int pageSize) { - KUrl url = createUrl( "friend/data/" + id ); + QUrl url = createUrl( "friend/data/" + id ); url.addQueryItem("page", QString::number(page)); url.addQueryItem("pagesize", QString::number(pageSize)); kDebug() << "URL:" << url; return doRequestPersonList( url ); } ActivityListJob* Provider::requestActivity() { - KUrl url = createUrl( "activity" ); + QUrl url = createUrl( "activity" ); return doRequestActivityList( url ); } PostJob* Provider::postActivity(const QString& message) { PostJob *job = new PostJob(); - KUrl url = createUrl( "activity" ); + QUrl url = createUrl( "activity" ); job->setUrl( url ); job->setData( "message", message ); job->start(); return job; } PostJob* Provider::postInvitation(const QString& to, const QString& message) { PostJob *job = new PostJob(); - KUrl url = createUrl( "friend/outbox/" + to ); + QUrl url = createUrl( "friend/outbox/" + to ); job->setUrl( url ); job->setData( "message", message ); job->start(); return job; } PostJob* Provider::postLocation(qreal latitude, qreal longitude, const QString& city, const QString& country) { PostJob *job = new PostJob(); - KUrl url = createUrl( "person/self" ); + QUrl url = createUrl( "person/self" ); job->setUrl( url ); job->setData( "latitude", QString("%1").arg(latitude) ); job->setData( "longitude", QString("%1").arg(longitude) ); job->setData( "city", city ); job->setData( "country", country ); job->start(); return job; } FolderListJob* Provider::requestFolders() { return doRequestFolderList( createUrl( "message" ) ); } MessageListJob* Provider::requestMessages(const QString& folderId) { return doRequestMessageList( createUrl( "message/" + folderId ) ); } PostJob* Provider::postMessage( const Message &message ) { PostJob *job = new PostJob(); - KUrl url = createUrl( "message/2" ); + QUrl url = createUrl( "message/2" ); job->setUrl( url ); job->setData( "message", message.body() ); job->setData( "subject", message.subject() ); job->setData( "to", message.to() ); job->start(); return job; } CategoryListJob* Provider::requestCategories() { CategoryListJob *job = new CategoryListJob(); - KUrl url = createUrl( "content/categories" ); + QUrl url = createUrl( "content/categories" ); job->setUrl( url ); job->start(); return job; } ContentListJob* Provider::requestContent(const Category::List& categories, const QString& search, SortMode sortMode) { ContentListJob *job = new ContentListJob(); - KUrl url = createUrl( "content/data" ); + QUrl url = createUrl( "content/data" ); QStringList categoryIds; foreach( const Category &category, categories ) { categoryIds.append( category.id() ); } url.addQueryItem( "categories", categoryIds.join( "x" ) ); url.addQueryItem( "search", search ); QString sortModeString; switch ( sortMode ) { case Newest: sortModeString = "new"; break; case Alphabetical: sortModeString = "alpha"; break; case Rating: sortModeString = "high"; break; case Downloads: sortModeString = "down"; break; } if ( !sortModeString.isEmpty() ) { url.addQueryItem( "sortmode", sortModeString ); } job->setUrl( url ); job->start(); return job; } ContentJob* Provider::requestContent(const QString& id) { ContentJob *job = new ContentJob(); - KUrl url = createUrl( "content/data/" + id ); + QUrl url = createUrl( "content/data/" + id ); job->setUrl( url ); job->start(); return job; } KnowledgeBaseJob* Provider::requestKnowledgeBase(const QString& id) { KnowledgeBaseJob *job = new KnowledgeBaseJob(); - KUrl url = createUrl( "knowledgebase/data/" + id ); + QUrl url = createUrl( "knowledgebase/data/" + id ); job->setUrl( url ); job->start(); return job; } KnowledgeBaseListJob* Provider::requestKnowledgeBase(int content, const QString& search, Provider::SortMode sortMode, int page, int pageSize) { KnowledgeBaseListJob *job = new KnowledgeBaseListJob(); - KUrl url = createUrl( "knowledgebase/data" ); + QUrl url = createUrl( "knowledgebase/data" ); if (content) { url.addQueryItem("content", QString::number(content)); } url.addQueryItem( "search", search ); QString sortModeString; switch ( sortMode ) { case Newest: sortModeString = "new"; break; case Alphabetical: sortModeString = "alpha"; break; case Rating: sortModeString = "high"; break; //FIXME: knowledge base doesn't have downloads case Downloads: sortModeString = "new"; break; } if ( !sortModeString.isEmpty() ) { url.addQueryItem( "sortmode", sortModeString ); } url.addQueryItem( "page", QString::number(page) ); url.addQueryItem( "pagesize", QString::number(pageSize) ); job->setUrl( url ); job->start(); return job; } EventJob* Provider::requestEvent(const QString& id) { EventJob* job = new EventJob(); job->setUrl(createUrl("event/data/" + id)); job->start(); return job; } EventListJob* Provider::requestEvent(const QString& country, const QString& search, const QDate& startAt, Provider::SortMode mode, int page, int pageSize) { EventListJob* job = new EventListJob(); - KUrl url = createUrl("event/data"); + QUrl url = createUrl("event/data"); if (!search.isEmpty()) { url.addQueryItem("search", search); } QString sortModeString; switch (mode) { case Newest: sortModeString = "new"; break; case Alphabetical: sortModeString = "alpha"; break; default: break; } if (!sortModeString.isEmpty()) { url.addQueryItem("sortmode", sortModeString); } if (!country.isEmpty()) { url.addQueryItem("country", country); } url.addQueryItem("startat", startAt.toString(Qt::ISODate)); url.addQueryItem("page", QString::number(page)); url.addQueryItem("pagesize", QString::number(pageSize)); job->setUrl(url); job->start(); return job; } -KUrl Provider::createUrl(const QString& path) const +QUrl Provider::createUrl(const QString& path) const { - KUrl url(d->m_baseUrl); - url.addPath( path ); + QUrl url(d->m_baseUrl); + url = url.adjusted(QUrl::StripTrailingSlash); + url.setPath(url.path() + '/' + ( path )); return url; } -PersonJob* Provider::doRequestPerson(const KUrl& url) const +PersonJob* Provider::doRequestPerson(const QUrl &url) const { PersonJob *job = new PersonJob(); job->setUrl( url ); job->start(); return job; } -PersonListJob* Provider::doRequestPersonList(const KUrl& url) +PersonListJob* Provider::doRequestPersonList(const QUrl &url) { PersonListJob *job = new PersonListJob(); job->setUrl( url ); job->start(); return job; } -ActivityListJob* Provider::doRequestActivityList(const KUrl& url) +ActivityListJob* Provider::doRequestActivityList(const QUrl &url) { ActivityListJob *job = new ActivityListJob(); job->setUrl( url ); job->start(); return job; } -FolderListJob* Provider::doRequestFolderList(const KUrl& url) +FolderListJob* Provider::doRequestFolderList(const QUrl &url) { FolderListJob *job = new FolderListJob(); job->setUrl( url ); job->start(); return job; } -MessageListJob* Provider::doRequestMessageList(const KUrl& url) +MessageListJob* Provider::doRequestMessageList(const QUrl &url) { MessageListJob *job = new MessageListJob(); job->setUrl( url ); job->start(); return job; } diff --git a/src/aboutdialog/libattica-ocsclient/provider.h b/src/aboutdialog/libattica-ocsclient/provider.h index 2b1d61fbdc..0f61116882 100644 --- a/src/aboutdialog/libattica-ocsclient/provider.h +++ b/src/aboutdialog/libattica-ocsclient/provider.h @@ -1,132 +1,132 @@ /* This file is part of KDE. Copyright (c) 2008 Cornelius Schumacher 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef ATTICA_PROVIDER_H #define ATTICA_PROVIDER_H #include #include #include "atticaclient_export.h" #include "category.h" -class KUrl; +class QUrl; class QDate; namespace AmarokAttica { class ActivityListJob; class CategoryListJob; class ContentJob; class ContentListJob; class EventJob; class EventListJob; class FolderListJob; class KnowledgeBaseJob; class KnowledgeBaseListJob; class Message; class MessageListJob; class PersonJob; class PersonListJob; class PostJob; class ProviderInitJob; /** Open Collaboration Services API. */ class ATTICA_EXPORT Provider { public: Provider(); Provider(const Provider& other); - Provider(const QString& id, const KUrl& baseUrl, const QString& name); + Provider(const QString& id, const QUrl &baseUrl, const QString& name); Provider& operator=(const Provider& other); ~Provider(); QString name() const; QString id() const; enum SortMode { Newest, Alphabetical, Rating, Downloads }; static ProviderInitJob* byId(const QString& id); // Person part of OCS PersonJob* requestPerson(const QString& id) const; PersonJob* requestPersonSelf(); PersonListJob* requestPersonSearchByName(const QString& name); PersonListJob* requestPersonSearchByLocation(qreal latitude, qreal longitude, qreal distance, int page = 0, int pageSize = 100); PostJob* postLocation(qreal latitude, qreal longitude, const QString& city = QString(), const QString& country = QString()); // Friend part of OCS PersonListJob* requestFriend(const QString& id, int page = 0, int pageSize = 100); PostJob* postInvitation(const QString& to, const QString& message); // Message part of OCS FolderListJob* requestFolders(); MessageListJob* requestMessages(const QString& folderId); PostJob* postMessage(const Message& message); // Activity part of OCS ActivityListJob* requestActivity(); PostJob* postActivity(const QString& message); // Content part of OCS CategoryListJob* requestCategories(); ContentListJob* requestContent(const Category::List& categories, const QString& search, SortMode mode); ContentJob* requestContent(const QString& id); // KnowledgeBase part of OCS KnowledgeBaseJob* requestKnowledgeBase(const QString& id); KnowledgeBaseListJob* requestKnowledgeBase(int content, const QString& search, SortMode, int page, int pageSize); // Event part of OCS EventJob* requestEvent(const QString& id); EventListJob* requestEvent(const QString& country, const QString& search, const QDate& startAt, SortMode mode, int page, int pageSize); protected: - KUrl createUrl(const QString& path) const; + QUrl createUrl(const QString& path) const; - PersonJob* doRequestPerson(const KUrl& url) const; - PersonListJob* doRequestPersonList(const KUrl& url); - ActivityListJob* doRequestActivityList(const KUrl& url); - FolderListJob* doRequestFolderList(const KUrl& url); - MessageListJob* doRequestMessageList(const KUrl& url); + PersonJob* doRequestPerson(const QUrl &url) const; + PersonListJob* doRequestPersonList(const QUrl &url); + ActivityListJob* doRequestActivityList(const QUrl &url); + FolderListJob* doRequestFolderList(const QUrl &url); + MessageListJob* doRequestMessageList(const QUrl &url); private: class Private; QExplicitlySharedDataPointer d; }; } #endif diff --git a/src/aboutdialog/libattica-ocsclient/providerinitjob.cpp b/src/aboutdialog/libattica-ocsclient/providerinitjob.cpp index 32adac1c83..03872af861 100644 --- a/src/aboutdialog/libattica-ocsclient/providerinitjob.cpp +++ b/src/aboutdialog/libattica-ocsclient/providerinitjob.cpp @@ -1,58 +1,58 @@ /* This file is part of KDE. Copyright (c) 2009 Eckhart Wörner 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "providerinitjob.h" #include -#include +#include using namespace AmarokAttica; ProviderInitJob::ProviderInitJob(const QString& id, QObject* parent) : KJob(parent), m_id(id) { } void ProviderInitJob::start() { QTimer::singleShot(0, this, SLOT(doWork())); } void ProviderInitJob::doWork() { if (m_id == "opendesktop") { - m_provider = Provider(m_id, KUrl("https://api.opendesktop.org/v1/"), "OpenDesktop.org"); + m_provider = Provider(m_id, QUrl("https://api.opendesktop.org/v1/"), "OpenDesktop.org"); } emitResult(); } Provider ProviderInitJob::provider() const { return m_provider; } diff --git a/src/amarokurls/PlayUrlRunner.cpp b/src/amarokurls/PlayUrlRunner.cpp index 3bce2580a4..9183dbf4ff 100644 --- a/src/amarokurls/PlayUrlRunner.cpp +++ b/src/amarokurls/PlayUrlRunner.cpp @@ -1,124 +1,124 @@ /**************************************************************************************** * Copyright (c) 2009 Casey Link * * * * 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, see . * ****************************************************************************************/ #include "PlayUrlRunner.h" #include "core/support/Debug.h" #include "amarokconfig.h" #include "AmarokUrl.h" #include "AmarokUrlHandler.h" #include "core-impl/storage/StorageManager.h" #include "core-impl/collections/support/CollectionManager.h" #include "EngineController.h" #include "playlist/PlaylistController.h" #include "playlist/PlaylistModelStack.h" #include "playlist/proxymodels/AbstractModel.h" #include #include "core/meta/Meta.h" PlayUrlRunner::PlayUrlRunner() : AmarokUrlRunnerBase() { } PlayUrlRunner::~PlayUrlRunner() { The::amarokUrlHandler()->unRegisterRunner ( this ); } bool PlayUrlRunner::run ( AmarokUrl url ) { DEBUG_BLOCK if ( url.isNull() ) return false; QUrl track_url = QUrl::fromEncoded ( QByteArray::fromBase64 ( url.path().toUtf8() ) ); debug() << "decoded track url: " << track_url.toString(); //get the position qint64 pos = 0; if ( url.args().keys().contains( "pos" ) ) { pos = (qint64) ( url.args().value( "pos" ).toDouble() * 1000.0 ); } debug() << "seek pos: " << pos; Meta::TrackPtr track = CollectionManager::instance()->trackForUrl( track_url ); if ( !track ) return false; The::engineController()->play ( track, pos ); Playlist::AbstractModel *model = The::playlist(); int row = model->firstRowForTrack( track ); if( row != -1 ) model->setActiveRow( row ); else { row = AmarokConfig::dynamicMode() ? model->activeRow() + 1 : model->qaim()->rowCount(); The::playlistController()->insertTrack( row, track ); model->setActiveRow( row ); } return true; } QString PlayUrlRunner::command() const { return "play"; } QString PlayUrlRunner::prettyCommand() const { return i18nc( "A type of command that starts playing at a specific position in a track", "Play" ); } -BookmarkList PlayUrlRunner::bookmarksFromUrl( KUrl url ) +BookmarkList PlayUrlRunner::bookmarksFromUrl( QUrl url ) { BookmarkList list; //See PlayUrlGenerator for the description of a 'play' amarokurl QString track_encoded = url.toEncoded().toBase64(); // The last character of a base64 encoded string is always '=', which // chokes the SQL. Since we are using a substring like text comparison // and every url in the database will have the '=', just chop it off. // some tracks even seem to have multiple '='s at the end... chop them all off! while( track_encoded.endsWith( '=' ) ) track_encoded.chop ( 1 ); // Queries the database for bookmarks where the url field contains // the base64 encoded url (minus the '='). QString query = "SELECT id, parent_id, name, url, description, custom FROM bookmarks WHERE url LIKE '%%1%'"; query = query.arg ( track_encoded ); QStringList result = StorageManager::instance()->sqlStorage()->query ( query ); int resultRows = result.count() / 6; for ( int i = 0; i < resultRows; i++ ) { QStringList row = result.mid ( i*6, 6 ); list << AmarokUrlPtr ( new AmarokUrl ( row ) ); } return list; } KIcon PlayUrlRunner::icon() const { return KIcon( "x-media-podcast-amarok" ); } diff --git a/src/amarokurls/PlayUrlRunner.h b/src/amarokurls/PlayUrlRunner.h index fb9800ca56..863fbc625c 100644 --- a/src/amarokurls/PlayUrlRunner.h +++ b/src/amarokurls/PlayUrlRunner.h @@ -1,53 +1,53 @@ /**************************************************************************************** * Copyright (c) 2009 Casey Link * * * * 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, see . * ****************************************************************************************/ #ifndef PLAYURLRUNNER_H #define PLAYURLRUNNER_H #include "amarok_export.h" #include "AmarokUrlRunnerBase.h" #include /** * The Runner that handles urls that plays tracks. * @author Casey Link */ class AMAROK_EXPORT PlayUrlRunner : public AmarokUrlRunnerBase { public: PlayUrlRunner (); virtual ~PlayUrlRunner (); virtual QString command () const; virtual QString prettyCommand() const; virtual bool run ( AmarokUrl url ); virtual KIcon icon () const; /** * This function takes a url for a track, and returns a list * of bookmarks (represented by play amarokurls) for the track. * The definition of a play amarokurl is in PlayUrlGenerator * * @param url the playableUrl() of a Meta::Track * @return a list of bookmarks. the list is empty if no bookmarks exist. * @see PlayUrlGenerator */ - static BookmarkList bookmarksFromUrl( KUrl url ); + static BookmarkList bookmarksFromUrl( QUrl url ); }; #endif // PLAYURLRUNNER_H diff --git a/src/app_mac.cpp b/src/app_mac.cpp index 0fce03b778..d4c3ae4c5a 100644 --- a/src/app_mac.cpp +++ b/src/app_mac.cpp @@ -1,97 +1,97 @@ /**************************************************************************************** * Copyright (c) 2007 Benjamin Reed * * * * 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, see . * ****************************************************************************************/ #include #include "amarokurls/AmarokUrl.h" #include "core-impl/collections/support/CollectionManager.h" #include "core/support/Debug.h" #include "core-impl/support/TrackLoader.h" #include "core/meta/Meta.h" #include "core/playlists/Playlist.h" #include "core-impl/playlists/types/file/PlaylistFileSupport.h" #include "playlist/PlaylistController.h" #include -#include +#include static AEEventHandlerUPP appleEventProcessorUPP = 0; static AEEventHandlerUPP macCallbackUrlHandlerUPP = 0; OSStatus appleEventProcessor(const AppleEvent *ae, AppleEvent *, long /*handlerRefCon*/) { OSType aeID = typeWildCard; OSType aeClass = typeWildCard; AEGetAttributePtr(ae, keyEventClassAttr, typeType, 0, &aeClass, sizeof(aeClass), 0); AEGetAttributePtr(ae, keyEventIDAttr, typeType, 0, &aeID, sizeof(aeID), 0); if(aeClass == kCoreEventClass) { if(aeID == kAEReopenApplication) { #if 0 if( PlaylistWindow::self() ) PlaylistWindow::self()->show(); #endif } return noErr; } return eventNotHandledErr; } OSStatus macCallbackUrlHandler( const AppleEvent *ae, AppleEvent *, long /*handlerRefCon*/) { DEBUG_BLOCK OSErr error = noErr; Size actualSize = 0; DescType descType = typeUTF8Text; if( ( error = AESizeOfParam( ae, keyDirectObject, &descType, &actualSize ) ) == noErr ) { QByteArray ba; ba.resize( actualSize + 1 ); error = AEGetParamPtr( ae, keyDirectObject, typeUTF8Text, 0, ba.data(), actualSize, &actualSize ); if( error == noErr ) { - KUrl url( QString::fromUtf8( ba.data() ) ); - if( url.protocol() == "amarok" ) + QUrl url( QString::fromUtf8( ba.data() ) ); + if( url.scheme() == "amarok" ) { AmarokUrl aUrl( url.url() ); aUrl.run(); } else { TrackLoader *loader = new TrackLoader(); // FIXME: this has no effect, one has to connect to finished() signal loader->init( url ); } } } return error; } void setupEventHandler_mac(SRefCon handlerRef) { appleEventProcessorUPP = AEEventHandlerUPP(appleEventProcessor); AEInstallEventHandler(kCoreEventClass, kAEReopenApplication, appleEventProcessorUPP, handlerRef, true); macCallbackUrlHandlerUPP = AEEventHandlerUPP(macCallbackUrlHandler); AEInstallEventHandler(kInternetEventClass, kAEGetURL, macCallbackUrlHandlerUPP, handlerRef, false); } diff --git a/src/browsers/BrowserCategory.cpp b/src/browsers/BrowserCategory.cpp index d7405c1079..b8057507c8 100644 --- a/src/browsers/BrowserCategory.cpp +++ b/src/browsers/BrowserCategory.cpp @@ -1,180 +1,180 @@ /**************************************************************************************** * Copyright (c) 2009 Nikolaj Hald Nielsen * * * * 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, see . * ****************************************************************************************/ #include "BrowserCategory.h" #include "App.h" #include "amarokconfig.h" #include "BrowserBreadcrumbItem.h" #include "BrowserCategoryList.h" #include "PaletteHandler.h" #include "core/support/Debug.h" #include BrowserCategory::BrowserCategory( const QString &name, QWidget *parent ) : KVBox( parent ) , m_name( name ) , m_parentList( 0 ) { setObjectName( name ); setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ); setFrameShape( QFrame::NoFrame ); connect( App::instance(), SIGNAL(settingsChanged()), SLOT(slotSettingsChanged()) ); connect( The::paletteHandler(), SIGNAL(newPalette(QPalette)), SLOT(slotSettingsChanged()) ); } BrowserCategory::~BrowserCategory() { } QString BrowserCategory::name() const { return m_name; } void BrowserCategory::setPrettyName( const QString &prettyName ) { m_prettyName = prettyName; } QString BrowserCategory::prettyName() const { return m_prettyName; } void BrowserCategory::setShortDescription( const QString &shortDescription ) { m_shortDescription = shortDescription; } QString BrowserCategory::shortDescription() const { return m_shortDescription; } void BrowserCategory::setLongDescription( const QString &longDescription ) { m_longDescription = longDescription; } QString BrowserCategory::longDescription() const { return m_longDescription; } void BrowserCategory::setIcon( const QIcon & icon ) { m_icon = icon; } QIcon BrowserCategory::icon() const { return m_icon; } void BrowserCategory::setBackgroundImage(const QString& path) { - if ( path.isEmpty() || !KUrl(path).isLocalFile() ) { + if ( path.isEmpty() || !QUrl(path).isLocalFile() ) { setStyleSheet( QString() ); return; } // Hack alert: Use the class name of the most derived object (using polymorphism) for CSS // This is required to limit the style to this specific class only (avoiding cascading) // \sa http://doc.qt.nokia.com/latest/stylesheet-syntax.html#widgets-inside-c-namespaces const QString escapedClassName = QString( metaObject()->className() ).replace( "::", "--" ); setStyleSheet( QString("%1 { background-image: url(\"%2\"); \ background-repeat: no-repeat; \ background-attachment: fixed; \ background-position: center; }").arg( escapedClassName, path ) ); } void BrowserCategory::slotSettingsChanged() { setBackgroundImage( AmarokConfig::showBrowserBackgroundImage() ? m_imagePath : QString() ); } void BrowserCategory::setParentList( BrowserCategoryList * parent ) { m_parentList = parent; } BrowserCategoryList * BrowserCategory::parentList() const { return m_parentList; } void BrowserCategory::activate() { DEBUG_BLOCK if ( parentList() ) parentList()->setActiveCategory( this ); } BrowserBreadcrumbItem *BrowserCategory::breadcrumb() { return new BrowserBreadcrumbItem( this ); } void BrowserCategory::setImagePath( const QString & path ) { m_imagePath = path; } QString BrowserCategory::imagePath() const { return m_imagePath; } void BrowserCategory::addAdditionalItem( BrowserBreadcrumbItem * item ) { m_additionalItems.append( item ); } void BrowserCategory::clearAdditionalItems() { foreach( BrowserBreadcrumbItem *item, m_additionalItems ) { m_additionalItems.removeAll( item ); /* deleting immediatelly isn't safe, this method may be called from an inner * QEventLoop inside QMenu::exec() of another breadcrumb item, which could * then leas to crash bug 265626 */ item->deleteLater(); } } QList BrowserCategory::additionalItems() { return m_additionalItems; } diff --git a/src/browsers/BrowserCategoryList.cpp b/src/browsers/BrowserCategoryList.cpp index 835a99bc76..cbd4140cff 100644 --- a/src/browsers/BrowserCategoryList.cpp +++ b/src/browsers/BrowserCategoryList.cpp @@ -1,420 +1,420 @@ /**************************************************************************************** * Copyright (c) 2009 Nikolaj Hald Nielsen * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "BrowserCategoryList" #include "BrowserCategoryList.h" #include "App.h" #include "context/ContextView.h" #include "core/support/Debug.h" #include "InfoProxy.h" #include "PaletteHandler.h" #include "widgets/PrettyTreeView.h" #include "widgets/PrettyTreeDelegate.h" #include "widgets/SearchWidget.h" #include #include #include #include #include BrowserCategoryList::BrowserCategoryList( const QString &name, QWidget* parent, bool sort ) : BrowserCategory( name, parent ) , m_categoryListModel( new BrowserCategoryListModel( this ) ) , m_sorting( sort ) { // -- the widget stack m_widgetStack = new QStackedWidget( this ); QWidget* mainWidget = new QWidget( m_widgetStack ); QVBoxLayout* vLayout = new QVBoxLayout( mainWidget ); mainWidget->setLayout( vLayout ); // -- the search widget m_searchWidget = new SearchWidget( this, false ); m_searchWidget->setClickMessage( i18n( "Filter Music Sources" ) ); vLayout->addWidget( m_searchWidget ); connect( m_searchWidget, SIGNAL(filterChanged(QString)), SLOT(setFilter(QString)) ); // -- the main list view m_categoryListView = new Amarok::PrettyTreeView(); m_categoryListView->setFrameShape( QFrame::NoFrame ); m_proxyModel = new BrowserCategoryListSortFilterProxyModel( this ); m_proxyModel->setSourceModel( m_categoryListModel ); m_categoryListView->setItemDelegate( new PrettyTreeDelegate( m_categoryListView ) ); m_categoryListView->setHeaderHidden( true ); m_categoryListView->setRootIsDecorated( false ); m_categoryListView->setModel( m_proxyModel ); m_categoryListView->setMouseTracking ( true ); if( sort ) { m_proxyModel->setSortRole( Qt::DisplayRole ); m_categoryListView->setSortingEnabled( true ); m_categoryListView->sortByColumn( 0 ); } connect( m_categoryListView, SIGNAL(activated(QModelIndex)), SLOT(categoryActivated(QModelIndex)) ); connect( m_categoryListView, SIGNAL(entered(QModelIndex)), SLOT(categoryEntered(QModelIndex)) ); vLayout->addWidget( m_categoryListView ); m_widgetStack->addWidget( mainWidget ); } BrowserCategoryList::~BrowserCategoryList() { } void BrowserCategoryList::categoryActivated( const QModelIndex &index ) { DEBUG_BLOCK BrowserCategory * category = 0; if( index.data( CustomCategoryRoles::CategoryRole ).canConvert() ) category = index.data( CustomCategoryRoles::CategoryRole ).value(); else return; if( category ) { debug() << "Show service: " << category->name(); setActiveCategory( category ); } } void BrowserCategoryList::home() { DEBUG_BLOCK if( activeCategory() ) { BrowserCategoryList *childList = qobject_cast( activeCategory() ); if( childList ) childList->home(); activeCategory()->clearAdditionalItems(); m_widgetStack->setCurrentIndex( 0 ); emit( viewChanged() ); } } QMap BrowserCategoryList::categories() { return m_categories; } void BrowserCategoryList::addCategory( BrowserCategory *category ) { Q_ASSERT( category ); category->setParentList( this ); //insert service into service map category->setParent( this ); m_categories[category->name()] = category; m_categoryListModel->addCategory( category ); m_widgetStack->addWidget( category ); //if this is also a category list, watch it for changes as we need to report //these down the tree BrowserCategoryList *childList = qobject_cast( category ); if ( childList ) connect( childList, SIGNAL(viewChanged()), this, SLOT(childViewChanged()) ); category->polish(); // service categories do an additional construction in polish if( m_sorting ) { m_proxyModel->sort( 0 ); } emit( viewChanged() ); } void BrowserCategoryList::removeCategory( BrowserCategory *category ) { Q_ASSERT( category ); if( m_widgetStack->indexOf( category ) == -1 ) return; // no such category if( m_widgetStack->currentWidget() == category ) home(); m_categories.remove( category->name() ); m_categoryListModel->removeCategory( category ); m_widgetStack->removeWidget( category ); delete category; m_categoryListView->reset(); emit( viewChanged() ); } BrowserCategory* BrowserCategoryList::activeCategory() const { return qobject_cast(m_widgetStack->currentWidget()); } void BrowserCategoryList::setActiveCategory( BrowserCategory* category ) { DEBUG_BLOCK; if( m_widgetStack->indexOf( category ) == -1 ) return; // no such category if( !category || activeCategory() == category ) return; // nothing to do if( activeCategory() ) activeCategory()->clearAdditionalItems(); category->setupAddItems(); m_widgetStack->setCurrentWidget( category ); emit( viewChanged() ); } void BrowserCategoryList::back() { DEBUG_BLOCK BrowserCategoryList *childList = qobject_cast( activeCategory() ); if( childList ) { if( childList->activeCategory() != 0 ) { childList->back(); return; } } home(); } void BrowserCategoryList::childViewChanged() { DEBUG_BLOCK emit( viewChanged() ); } QString BrowserCategoryList::navigate( const QString & target ) { DEBUG_BLOCK debug() << "target: " << target; QStringList categories = target.split( '/' ); if ( categories.size() == 0 ) return QString(); //remove our own name if present, before passing on... if ( categories.at( 0 ) == name() ) { debug() << "removing own name (" << categories.at( 0 ) << ") from path"; categories.removeFirst(); if ( categories.size() == 0 ) { //nothing else left, make sure this category is visible home(); return QString(); } } QString childName = categories.at( 0 ); debug() << "looking for child category " << childName; if ( !m_categories.contains( childName ) ) return target; debug() << "got it!"; setActiveCategory( m_categories[childName] ); //check if this category is also BrowserCategoryList.target BrowserCategoryList *childList = qobject_cast( activeCategory() ); if ( childList == 0 ) { debug() << "child is not a list..."; if ( categories.size() > 1 ) { categories.removeFirst(); QString leftover = categories.join( "/" ); return leftover; } return QString(); } //check if there are more arguments in the navigate string. if ( categories.size() == 1 ) { debug() << "Child is a list but path ends here..."; //only one name, but since the category we switched to is also //a category list, make sure that it is reset to home childList->home(); return QString(); } categories.removeFirst(); debug() << "passing remaining path to child: " << categories.join( "/" ); return childList->navigate( categories.join( "/" ) ); } QString BrowserCategoryList::path() { DEBUG_BLOCK QString pathString = name(); BrowserCategoryList *childList = qobject_cast( activeCategory() ); if( childList ) pathString += '/' + childList->path(); else if( activeCategory() ) pathString += '/' + activeCategory()->name(); debug() << "path: " << pathString; return pathString; } void BrowserCategoryList::categoryEntered( const QModelIndex & index ) { //get the long description for this item and pass it it to info proxy. BrowserCategory *category = 0; if ( index.data( CustomCategoryRoles::CategoryRole ).canConvert() ) category = index.data( CustomCategoryRoles::CategoryRole ).value(); else return; if( category ) { //instead of just throwing out raw text, let's format the long description and the //icon into a nice html page. if ( m_infoHtmlTemplate.isEmpty() ) { - KUrl dataUrl( KStandardDirs::locate( "data", "amarok/data/" ) ); + QUrl dataUrl( KStandardDirs::locate( "data", "amarok/data/" ) ); QString dataPath = dataUrl.path(); //load html QString htmlPath = dataPath + "hover_info_template.html"; QFile file( htmlPath ); if ( !file.open( QIODevice::ReadOnly | QIODevice::Text) ) { debug() << "error opening file. Error: " << file.error(); return; } m_infoHtmlTemplate = file.readAll(); file.close(); m_infoHtmlTemplate.replace( "{background_color}",PaletteHandler::highlightColor().lighter( 150 ).name() ); m_infoHtmlTemplate.replace( "{border_color}", PaletteHandler::highlightColor().lighter( 150 ).name() ); m_infoHtmlTemplate.replace( "{text_color}", App::instance()->palette().brush( QPalette::Text ).color().name() ); QColor highlight( App::instance()->palette().highlight().color() ); highlight.setHsvF( highlight.hueF(), 0.3, .95, highlight.alphaF() ); m_infoHtmlTemplate.replace( "{header_background_color}", highlight.name() ); } QString currentHtml = m_infoHtmlTemplate; currentHtml.replace( "%%NAME%%", category->prettyName() ); currentHtml.replace( "%%DESCRIPTION%%", category->longDescription() ); currentHtml.replace( "%%IMAGE_PATH%%", "file://" + category->imagePath() ); QVariantMap variantMap; variantMap["main_info"] = QVariant( currentHtml ); The::infoProxy()->setInfo( variantMap ); } } QString BrowserCategoryList::css() { QString style = ""; return style; } BrowserCategory *BrowserCategoryList::activeCategoryRecursive() { BrowserCategory *category = activeCategory(); if( !category ) return this; BrowserCategoryList *childList = qobject_cast( category ); if( childList ) return childList->activeCategoryRecursive(); return category; } void BrowserCategoryList::setFilter( const QString &filter ) { m_proxyModel->setFilterFixedString( filter ); } diff --git a/src/browsers/CollectionTreeItem.cpp b/src/browsers/CollectionTreeItem.cpp index 6b60f9b00f..d0d14e5b65 100644 --- a/src/browsers/CollectionTreeItem.cpp +++ b/src/browsers/CollectionTreeItem.cpp @@ -1,402 +1,402 @@ /**************************************************************************************** * Copyright (c) 2007 Alexandre Pereira de Oliveira * * Copyright (c) 2007-2009 Maximilian Kossick * * Copyright (c) 2009 Seb Ruiz * * * * 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, see . * ****************************************************************************************/ #include "CollectionTreeItem.h" #include "amarokconfig.h" #include "browsers/CollectionTreeItemModelBase.h" #include "core/capabilities/ActionsCapability.h" #include "core/meta/Meta.h" #include "core/support/Debug.h" #include "widgets/PrettyTreeRoles.h" #include #include Q_DECLARE_METATYPE( QAction* ) Q_DECLARE_METATYPE( QList ) CollectionTreeItem::CollectionTreeItem( CollectionTreeItemModelBase *model ) : m_data( 0 ) , m_parent( 0 ) , m_model( model ) , m_parentCollection( 0 ) , m_updateRequired( false ) , m_trackCount( -1 ) , m_type( Root ) //, m_name( "Root" ) , m_isCounting( false ) { } CollectionTreeItem::CollectionTreeItem( Meta::DataPtr data, CollectionTreeItem *parent, CollectionTreeItemModelBase *model ) : m_data( data ) , m_parent( parent ) , m_model( model ) , m_parentCollection( 0 ) , m_updateRequired( true ) , m_trackCount( -1 ) , m_type( Data ) //, m_name( data ? data->name() : "NullData" ) , m_isCounting( false ) { if ( m_parent ) m_parent->appendChild( this ); } CollectionTreeItem::CollectionTreeItem( Collections::Collection *parentCollection, CollectionTreeItem *parent, CollectionTreeItemModelBase *model ) : m_data( 0 ) , m_parent( parent ) , m_model( model ) , m_parentCollection( parentCollection ) , m_updateRequired( true ) , m_trackCount( -1 ) , m_type( Collection ) //, m_name( parentCollection ? parentCollection->collectionId() : "NullColl" ) , m_isCounting( false ) { if ( m_parent ) m_parent->appendChild( this ); connect( parentCollection, SIGNAL(updated()), SLOT(collectionUpdated()) ); } CollectionTreeItem::CollectionTreeItem( Type type, const Meta::DataList &data, CollectionTreeItem *parent, CollectionTreeItemModelBase *model ) : m_data( 0 ) , m_parent( parent ) , m_model( model ) , m_parentCollection( 0 ) , m_updateRequired( false ) //the node already has all children , m_trackCount( -1 ) , m_type( type ) , m_isCounting( false ) { if( m_parent ) m_parent->m_childItems.insert( 0, this ); foreach( Meta::DataPtr datap, data ) new CollectionTreeItem( datap, this, m_model ); } CollectionTreeItem::~CollectionTreeItem() { qDeleteAll( m_childItems ); } void CollectionTreeItem::appendChild(CollectionTreeItem *child) { m_childItems.append(child); } void CollectionTreeItem::removeChild( int index ) { CollectionTreeItem *child = m_childItems[index]; m_childItems.removeAt( index ); child->prepareForRemoval(); child->deleteLater(); } void CollectionTreeItem::prepareForRemoval() { m_parent = 0; m_model->itemAboutToBeDeleted( this ); foreach( CollectionTreeItem *item, m_childItems ) { item->prepareForRemoval(); } } CollectionTreeItem* CollectionTreeItem::child( int row ) { return m_childItems.value( row ); } QVariant CollectionTreeItem::data( int role ) const { if( isNoLabelItem() ) { switch( role ) { case Qt::DisplayRole: return i18nc( "No labels are assigned to the given item are any of its subitems", "No Labels" ); case Qt::DecorationRole: return KIcon( "label-amarok" ); } return QVariant(); } else if( m_parentCollection ) { static const QString counting = i18n( "Counting..." ); switch( role ) { case Qt::DisplayRole: case PrettyTreeRoles::FilterRole: case PrettyTreeRoles::SortRole: return m_parentCollection->prettyName(); case Qt::DecorationRole: return m_parentCollection->icon(); case PrettyTreeRoles::ByLineRole: if( m_isCounting ) return counting; if( m_trackCount < 0 ) { m_isCounting = true; Collections::QueryMaker *qm = m_parentCollection->queryMaker(); connect( qm, SIGNAL(newResultReady(QStringList)), SLOT(tracksCounted(QStringList)) ); qm->setAutoDelete( true ) ->setQueryType( Collections::QueryMaker::Custom ) ->addReturnFunction( Collections::QueryMaker::Count, Meta::valUrl ) ->run(); return counting; } return i18np( "1 track", "%1 tracks", m_trackCount ); case PrettyTreeRoles::HasCapacityRole: return m_parentCollection->hasCapacity(); case PrettyTreeRoles::UsedCapacityRole: return m_parentCollection->usedCapacity(); case PrettyTreeRoles::TotalCapacityRole: return m_parentCollection->totalCapacity(); case PrettyTreeRoles::DecoratorRoleCount: return decoratorActions().size(); case PrettyTreeRoles::DecoratorRole: QVariant v; v.setValue( decoratorActions() ); return v; } } return QVariant(); } QList CollectionTreeItem::decoratorActions() const { QList decoratorActions; QScopedPointer dc( m_parentCollection->create() ); if( dc ) decoratorActions = dc->actions(); return decoratorActions; } void CollectionTreeItem::tracksCounted( QStringList res ) { if( !res.isEmpty() ) m_trackCount = res.first().toInt(); else m_trackCount = 0; m_isCounting = false; emit dataUpdated(); } void CollectionTreeItem::collectionUpdated() { m_trackCount = -1; } int CollectionTreeItem::row() const { if( m_parent ) { const QList &children = m_parent->m_childItems; if( !children.isEmpty() && children.contains( const_cast(this) ) ) return children.indexOf( const_cast(this) ); else return -1; } return 0; } int CollectionTreeItem::level() const { if( m_parent ) return m_parent->level() + 1; return -1; } bool CollectionTreeItem::isDataItem() const { return m_type == Data; } bool CollectionTreeItem::isVariousArtistItem() const { return m_type == CollectionTreeItem::VariousArtist; } bool CollectionTreeItem::isNoLabelItem() const { return m_type == CollectionTreeItem::NoLabel; } bool CollectionTreeItem::isAlbumItem() const { return m_type == Data && !Meta::AlbumPtr::dynamicCast( m_data ).isNull(); } bool CollectionTreeItem::isTrackItem() const { return m_type == Data && !Meta::TrackPtr::dynamicCast( m_data ).isNull(); } Collections::QueryMaker* CollectionTreeItem::queryMaker() const { Collections::Collection* coll = parentCollection(); if( coll ) return coll->queryMaker(); return 0; } void CollectionTreeItem::addMatch( Collections::QueryMaker *qm, CategoryId::CatMenuId levelCategory ) const { if( !qm ) return; if( isVariousArtistItem() ) qm->setAlbumQueryMode( Collections::QueryMaker::OnlyCompilations ); if( isNoLabelItem() ) qm->setLabelQueryMode( Collections::QueryMaker::OnlyWithoutLabels ); else if( Meta::TrackPtr track = Meta::TrackPtr::dynamicCast( m_data ) ) qm->addMatch( track ); else if( Meta::ArtistPtr artist = Meta::ArtistPtr::dynamicCast( m_data ) ) { Collections::QueryMaker::ArtistMatchBehaviour behaviour = ( levelCategory == CategoryId::AlbumArtist ) ? Collections::QueryMaker::AlbumArtists : Collections::QueryMaker::TrackArtists; qm->addMatch( artist, behaviour ); } else if( Meta::AlbumPtr album = Meta::AlbumPtr::dynamicCast( m_data ) ) qm->addMatch( album ); else if( Meta::ComposerPtr composer = Meta::ComposerPtr::dynamicCast( m_data ) ) qm->addMatch( composer ); else if( Meta::GenrePtr genre = Meta::GenrePtr::dynamicCast( m_data ) ) qm->addMatch( genre ); else if( Meta::YearPtr year = Meta::YearPtr::dynamicCast( m_data ) ) qm->addMatch( year ); else if( Meta::LabelPtr label = Meta::LabelPtr::dynamicCast( m_data ) ) qm->addMatch( label ); } -KUrl::List +QList CollectionTreeItem::urls() const { /*QueryBuilder qb = queryBuilder(); qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL ); QStringList values = qb.run(); - KUrl::List list; + QList list; foreach( QString s, values ) { - list += KUrl( s ); + list += QUrl( s ); } return list;*/ - KUrl::List list; + QList list; return list; } bool CollectionTreeItem::operator<( const CollectionTreeItem& other ) const { if( isVariousArtistItem() ) return true; return m_data->sortableName() < other.m_data->sortableName(); } const Meta::DataPtr CollectionTreeItem::data() const { return m_data; } Meta::TrackList CollectionTreeItem::descendentTracks() { QList descendentTracks; Meta::TrackPtr track; if( isDataItem() ) track = Meta::TrackPtr::dynamicCast( m_data ); if( !track.isNull() ) descendentTracks << track; else { foreach( CollectionTreeItem *child, m_childItems ) descendentTracks << child->descendentTracks(); } return descendentTracks; } bool CollectionTreeItem::allDescendentTracksLoaded() const { if( isTrackItem() ) return true; if( requiresUpdate() ) return false; foreach( CollectionTreeItem *item, m_childItems ) { if( !item->allDescendentTracksLoaded() ) return false; } return true; } void CollectionTreeItem::setRequiresUpdate( bool updateRequired ) { m_updateRequired = updateRequired; } bool CollectionTreeItem::requiresUpdate() const { return m_updateRequired; } CollectionTreeItem::Type CollectionTreeItem::type() const { return m_type; } QList CollectionTreeItem::children() const { return m_childItems; } diff --git a/src/browsers/CollectionTreeItem.h b/src/browsers/CollectionTreeItem.h index 0e6c801b85..98cdf583e9 100644 --- a/src/browsers/CollectionTreeItem.h +++ b/src/browsers/CollectionTreeItem.h @@ -1,136 +1,136 @@ /**************************************************************************************** * Copyright (c) 2007 Alexandre Pereira de Oliveira * * Copyright (c) 2007-2009 Maximilian Kossick * * * * 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, see . * ****************************************************************************************/ #ifndef COLLECTIONTREEITEM_H #define COLLECTIONTREEITEM_H #include "amarok_export.h" #include "browsers/BrowserDefines.h" #include "core/collections/Collection.h" #include "core/meta/forward_declarations.h" #include class CollectionTreeItemModelBase; class QAction; class AMAROK_EXPORT CollectionTreeItem : public QObject { Q_OBJECT Q_ENUMS( Type ) public: enum Type { Root, Collection, VariousArtist, NoLabel, Data }; CollectionTreeItem( CollectionTreeItemModelBase *model ); //root node CollectionTreeItem( Meta::DataPtr data, CollectionTreeItem *parent, CollectionTreeItemModelBase *model ); //data node CollectionTreeItem( Collections::Collection *parentCollection, CollectionTreeItem *parent, CollectionTreeItemModelBase *model ); //collection node //this ctor creates a "Various Artists" and "No Labels" nodes. do not use it for anything else CollectionTreeItem( Type type, const Meta::DataList &data, CollectionTreeItem *parent, CollectionTreeItemModelBase *model ); //various artist node ~CollectionTreeItem(); CollectionTreeItem* parent() const { return m_parent; } void appendChild( CollectionTreeItem *child ); void removeChild( int index ); CollectionTreeItem* child( int row ); int childCount() const { return m_childItems.count(); } int columnCount() const { return 1; } QList children() const; QVariant data( int role ) const; int row() const; int level() const; bool isDataItem() const; bool isAlbumItem() const; bool isTrackItem() const; bool isVariousArtistItem() const; bool isNoLabelItem() const; Collections::QueryMaker* queryMaker() const; /** * Call addMatch for this objects data and it's query maker. Handles VariousArtist * item and NoLabel item, too. * * @param qm QueryMaker to add match to * @param levelCategory category for level this item is in, one of the values from * CategoryId::CatMenuId enum. Used only for distinction between Artist and * AlbumArtist. */ void addMatch( Collections::QueryMaker *qm, CategoryId::CatMenuId levelCategory ) const; bool operator<( const CollectionTreeItem &other ) const; const Meta::DataPtr data() const; Collections::Collection* parentCollection() const { return m_parentCollection ? m_parentCollection : (m_parent ? m_parent->parentCollection() : 0); } - KUrl::List urls() const; + QList urls() const; Meta::TrackList descendentTracks(); bool allDescendentTracksLoaded() const; //required to mark a tree item as dirty if the model has to requiry its childre Type type() const; bool requiresUpdate() const; void setRequiresUpdate( bool updateRequired ); signals: void dataUpdated(); private slots: void tracksCounted( QStringList res ); void collectionUpdated(); private: /** Returns a list of collection actions. Collection actions are shown on top of the collection tree item as icons (decorations) */ QList decoratorActions() const; void prepareForRemoval(); Meta::DataPtr m_data; CollectionTreeItem *m_parent; CollectionTreeItemModelBase *m_model; Collections::Collection *m_parentCollection; QList m_childItems; bool m_updateRequired; int m_trackCount; Type m_type; //QString m_name; mutable bool m_isCounting; }; Q_DECLARE_METATYPE( CollectionTreeItem* ) Q_DECLARE_METATYPE( QList ) #endif diff --git a/src/browsers/InfoProxy.cpp b/src/browsers/InfoProxy.cpp index 1785878a4f..7d6d33344e 100644 --- a/src/browsers/InfoProxy.cpp +++ b/src/browsers/InfoProxy.cpp @@ -1,174 +1,174 @@ /**************************************************************************************** * Copyright (c) 2007 Nikolaj Hald Nielsen * * * * 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, see . * ****************************************************************************************/ #include "InfoProxy.h" #include "App.h" #include "core/support/Debug.h" #include "PaletteHandler.h" #include #include InfoProxy * InfoProxy::m_instance = 0; InfoProxy * InfoProxy::instance() { if ( m_instance == 0 ) m_instance = new InfoProxy(); return m_instance; } InfoProxy::InfoProxy() { DEBUG_BLOCK; //for testing QList strings; QList weights; strings << "This" << "is" << "just" << "a" << "very" << "small" << "and" << "quite" << "silly" << "defalt" << "text" << "as" << "I" << "currently" << "have" << "nothing" << "better" << "to" << "show"; weights << 10 << 4 << 8 << 2 << 6 << 5 << 10 << 9 << 3 << 1 << 3 << 5 << 7 << 9 << 3 << 2 << 10 << 6 << 4; m_storedCloud["cloud_name"] = QVariant( "test cloud" ); m_storedCloud["cloud_strings"] = QVariant( strings ); m_storedCloud["cloud_weights"] = QVariant( weights ); loadHomePage(); } InfoProxy::~InfoProxy() { } void InfoProxy::subscribe( InfoObserver * observer ) { DEBUG_BLOCK; if( observer ) { m_observers.insert( observer ); observer->infoChanged( m_storedInfo ); } } void InfoProxy::subscribeForCloud( InfoObserver * observer ) { DEBUG_BLOCK; if( observer ) { m_cloudObservers.insert( observer ); observer->infoChanged( m_storedCloud ); } } void InfoProxy::unsubscribe( InfoObserver * observer ) { m_observers.remove( observer ); m_cloudObservers.remove( observer ); } void InfoProxy::notifyObservers( const QVariantMap &infoMap ) const { foreach( InfoObserver *observer, m_observers ) observer->infoChanged( infoMap ); } void InfoProxy::notifyCloudObservers( const QVariantMap &cloudMap ) const { foreach( InfoObserver *observer, m_cloudObservers ) observer->infoChanged( cloudMap ); } void InfoProxy::setInfo( const QVariantMap &infoMap ) { m_storedInfo = infoMap; notifyObservers( m_storedInfo ); } void InfoProxy::setCloud( const QVariantMap &cloudMap ) { m_storedCloud = cloudMap; notifyCloudObservers( m_storedCloud ); } QVariantMap InfoProxy::info() { return m_storedInfo; } QVariantMap InfoProxy::cloud() { return m_storedCloud; } void InfoProxy::loadHomePage() { DEBUG_BLOCK - KUrl dataUrl( KStandardDirs::locate( "data", "amarok/data/" ) ); + QUrl dataUrl( KStandardDirs::locate( "data", "amarok/data/" ) ); QString dataPath = dataUrl.path(); //load html QString htmlPath = dataPath + "info_frontpage.html"; QFile file( htmlPath ); if ( !file.open( QIODevice::ReadOnly | QIODevice::Text) ) { debug() << "error opening file. Error: " << file.error(); return; } QString html = file.readAll(); - KUrl imageUrl( KStandardDirs::locate( "data", "amarok/images/" ) ); + QUrl imageUrl( KStandardDirs::locate( "data", "amarok/images/" ) ); QString imagePath = imageUrl.url(); html.replace( "_PATH_", imagePath ); html.replace( "{background_color}",PaletteHandler::highlightColor().lighter( 150 ).name() ); html.replace( "{border_color}", PaletteHandler::highlightColor().lighter( 150 ).name() ); html.replace( "{text_color}", App::instance()->palette().brush( QPalette::Text ).color().name() ); QColor highlight( App::instance()->palette().highlight().color() ); highlight.setHsvF( highlight.hueF(), 0.3, .95, highlight.alphaF() ); html.replace( "{header_background_color}", highlight.name() ); m_storedInfo["service_name"] = i18n( "Home" ); m_storedInfo["main_info"] = html; notifyObservers( m_storedInfo ); } namespace The { AMAROK_EXPORT InfoProxy* infoProxy() { return InfoProxy::instance(); } } diff --git a/src/browsers/filebrowser/FileBrowser.cpp b/src/browsers/filebrowser/FileBrowser.cpp index f6e4f4e4ca..76477d8e98 100644 --- a/src/browsers/filebrowser/FileBrowser.cpp +++ b/src/browsers/filebrowser/FileBrowser.cpp @@ -1,629 +1,629 @@ /**************************************************************************************** * Copyright (c) 2010 Nikolaj Hald Nielsen * * Copyright (c) 2010 Casey Link * * Copyright (c) 2010 Téo Mrnjavac * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "FileBrowser" #include "FileBrowser.h" #include "FileBrowser_p.h" #include "FileBrowser_p.moc" #include "amarokconfig.h" #include "EngineController.h" #include "core/support/Amarok.h" #include "core/support/Components.h" #include "core/support/Debug.h" #include "core-impl/meta/file/File.h" #include "browsers/BrowserBreadcrumbItem.h" #include "browsers/BrowserCategoryList.h" #include "browsers/filebrowser/DirPlaylistTrackFilterProxyModel.h" #include "browsers/filebrowser/FileView.h" #include "playlist/PlaylistController.h" #include "widgets/SearchWidget.h" #include #include #include #include #include #include #include #include #include #include static const QString placesString( "places://" ); -static const KUrl placesUrl( placesString ); +static const QUrl placesUrl( placesString ); FileBrowser::Private::Private( FileBrowser *parent ) : placesModel( 0 ) , q( parent ) { KHBox *topHBox = new KHBox( q ); KToolBar *navigationToolbar = new KToolBar( topHBox ); navigationToolbar->setToolButtonStyle( Qt::ToolButtonIconOnly ); navigationToolbar->setIconDimensions( 16 ); backAction = KStandardAction::back( q, SLOT(back()), topHBox ); forwardAction = KStandardAction::forward( q, SLOT(forward()), topHBox ); backAction->setEnabled( false ); forwardAction->setEnabled( false ); upAction = KStandardAction::up( q, SLOT(up()), topHBox ); homeAction = KStandardAction::home( q, SLOT(home()), topHBox ); refreshAction = new KAction( KIcon("view-refresh"), i18n( "Refresh" ), topHBox ); QObject::connect( refreshAction, SIGNAL(triggered(bool)), q, SLOT(refresh()) ); navigationToolbar->addAction( backAction ); navigationToolbar->addAction( forwardAction ); navigationToolbar->addAction( upAction ); navigationToolbar->addAction( homeAction ); navigationToolbar->addAction( refreshAction ); searchWidget = new SearchWidget( topHBox, false ); searchWidget->setClickMessage( i18n( "Filter Files" ) ); fileView = new FileView( q ); } FileBrowser::Private::~Private() { writeConfig(); } void FileBrowser::Private::readConfig() { - const KUrl homeUrl( QDir::homePath() ); - const KUrl savedUrl = Amarok::config( "File Browser" ).readEntry( "Current Directory", homeUrl ); + const QUrl homeUrl( QDir::homePath() ); + const QUrl savedUrl = Amarok::config( "File Browser" ).readEntry( "Current Directory", homeUrl ); bool useHome( true ); // fall back to $HOME if the saved dir has since disappeared or is a remote one if( savedUrl.isLocalFile() ) { QDir dir( savedUrl.path() ); if( dir.exists() ) useHome = false; } else if( KIO::NetAccess::exists( savedUrl, KIO::NetAccess::DestinationSide, 0 ) ) { useHome = false; } currentPath = useHome ? homeUrl : savedUrl; } void FileBrowser::Private::writeConfig() { Amarok::config( "File Browser" ).writeEntry( "Current Directory", kdirModel->dirLister()->url() ); } BreadcrumbSiblingList -FileBrowser::Private::siblingsForDir( const KUrl &path ) +FileBrowser::Private::siblingsForDir( const QUrl &path ) { BreadcrumbSiblingList siblings; - if( path.protocol() == "places" ) + if( path.scheme() == "places" ) { for( int i = 0; i < placesModel->rowCount(); i++ ) { QModelIndex idx = placesModel->index( i, 0 ); QString name = idx.data( Qt::DisplayRole ).toString(); QString url = idx.data( KFilePlacesModel::UrlRole ).toString(); if( url.isEmpty() ) // the place perhaps needs mounting, use places url instead url = placesString + name; siblings << BreadcrumbSibling( idx.data( Qt::DecorationRole ).value(), name, url ); } } else if( path.isLocalFile() ) { QDir dir( path.toLocalFile() ); dir.cdUp(); foreach( const QString &item, dir.entryList( QDir::Dirs | QDir::NoDotAndDotDot ) ) { siblings << BreadcrumbSibling( KIcon( "folder-amarok" ), item, dir.absoluteFilePath( item ) ); } } return siblings; } void FileBrowser::Private::updateNavigateActions() { backAction->setEnabled( !backStack.isEmpty() ); forwardAction->setEnabled( !forwardStack.isEmpty() ); upAction->setEnabled( currentPath != placesUrl ); } void FileBrowser::Private::restoreDefaultHeaderState() { fileView->hideColumn( 3 ); fileView->hideColumn( 4 ); fileView->hideColumn( 5 ); fileView->hideColumn( 6 ); fileView->sortByColumn( 0, Qt::AscendingOrder ); } void FileBrowser::Private::restoreHeaderState() { QFile file( Amarok::saveLocation() + "file_browser_layout" ); if( !file.open( QIODevice::ReadOnly ) ) { restoreDefaultHeaderState(); return; } if( !fileView->header()->restoreState( file.readAll() ) ) { warning() << "invalid header state saved, unable to restore. Restoring defaults"; restoreDefaultHeaderState(); return; } } void FileBrowser::Private::saveHeaderState() { //save the state of the header (column size and order). Yay, another QByteArray thingie... KSaveFile file( Amarok::saveLocation() + "file_browser_layout" ); if( !file.open( QIODevice::WriteOnly ) ) { warning() << "unable to save header state"; return; } if( file.write( fileView->header()->saveState() ) < 0 ) { warning() << "unable to save header state, writing failed"; return; } if( !file.finalize() ) { warning() << "failed to write header state"; return; } } void FileBrowser::Private::updateHeaderState() { // this slot is triggered right after model change, when currentPath is not yet updated if( fileView->model() == mimeFilterProxyModel && currentPath == placesUrl ) // we are transitioning from places to files restoreHeaderState(); } FileBrowser::FileBrowser( const char *name, QWidget *parent ) : BrowserCategory( name, parent ) , d( new FileBrowser::Private( this ) ) { setLongDescription( i18n( "The file browser lets you browse files anywhere on your system, " "regardless of whether these files are part of your local collection. " "You can then add these files to the playlist as well as perform basic " "file operations." ) ); setImagePath( KStandardDirs::locate( "data", "amarok/images/hover_info_files.png" ) ); // set background if( AmarokConfig::showBrowserBackgroundImage() ) setBackgroundImage( imagePath() ); initView(); } void FileBrowser::initView() { d->bottomPlacesModel = new FilePlacesModel( this ); connect( d->bottomPlacesModel, SIGNAL(setupDone(QModelIndex,bool)), SLOT(setupDone(QModelIndex,bool)) ); d->placesModel = new QSortFilterProxyModel( this ); d->placesModel->setSourceModel( d->bottomPlacesModel ); d->placesModel->setSortRole( -1 ); d->placesModel->setDynamicSortFilter( true ); d->placesModel->setFilterRole( KFilePlacesModel::HiddenRole ); // HiddenRole is bool, but QVariant( false ).toString() gives "false" d->placesModel->setFilterFixedString( "false" ); d->placesModel->setObjectName( "PLACESMODEL"); d->kdirModel = new DirBrowserModel( this ); d->mimeFilterProxyModel = new DirPlaylistTrackFilterProxyModel( this ); d->mimeFilterProxyModel->setSourceModel( d->kdirModel ); d->mimeFilterProxyModel->setSortCaseSensitivity( Qt::CaseInsensitive ); d->mimeFilterProxyModel->setFilterCaseSensitivity( Qt::CaseInsensitive ); d->mimeFilterProxyModel->setDynamicSortFilter( true ); connect( d->searchWidget, SIGNAL(filterChanged(QString)), d->mimeFilterProxyModel, SLOT(setFilterFixedString(QString)) ); d->fileView->setModel( d->mimeFilterProxyModel ); d->fileView->header()->setContextMenuPolicy( Qt::ActionsContextMenu ); d->fileView->header()->setVisible( true ); d->fileView->setDragEnabled( true ); d->fileView->setSortingEnabled( true ); d->fileView->setSelectionMode( QAbstractItemView::ExtendedSelection ); d->readConfig(); d->restoreHeaderState(); setDir( d->currentPath ); for( int i = 0, columns = d->fileView->model()->columnCount(); i < columns ; ++i ) { QAction *action = new QAction( d->fileView->model()->headerData( i, Qt::Horizontal ).toString(), d->fileView->header() ); d->fileView->header()->addAction( action ); d->columnActions.append( action ); action->setCheckable( true ); if( !d->fileView->isColumnHidden( i ) ) action->setChecked( true ); connect( action, SIGNAL(toggled(bool)), this, SLOT(toggleColumn(bool)) ); } connect( d->fileView->header(), SIGNAL(geometriesChanged()), SLOT(updateHeaderState()) ); connect( d->fileView, SIGNAL(navigateToDirectory(QModelIndex)), SLOT(slotNavigateToDirectory(QModelIndex)) ); connect( d->fileView, SIGNAL(refreshBrowser()), SLOT(refresh()) ); } FileBrowser::~FileBrowser() { if( d->fileView->model() == d->mimeFilterProxyModel && d->currentPath != placesUrl ) d->saveHeaderState(); delete d; } void FileBrowser::toggleColumn( bool toggled ) { int index = d->columnActions.indexOf( qobject_cast< QAction* >( sender() ) ); if( index != -1 ) { if( toggled ) d->fileView->showColumn( index ); else d->fileView->hideColumn( index ); } } QString FileBrowser::currentDir() const { if( d->currentPath.isLocalFile() ) return d->currentPath.toLocalFile(); else return d->currentPath.url(); } void FileBrowser::slotNavigateToDirectory( const QModelIndex &index ) { if( d->currentPath == placesUrl ) { QString url = index.data( KFilePlacesModel::UrlRole ).value(); if( !url.isEmpty() ) { d->backStack.push( d->currentPath ); d->forwardStack.clear(); // navigating resets forward stack - setDir( KUrl( url ) ); + setDir( QUrl( url ) ); } else { //check if this url needs setup/mounting if( index.data( KFilePlacesModel::SetupNeededRole ).value() ) { d->bottomPlacesModel->requestSetup( d->placesModel->mapToSource( index ) ); } else warning() << __PRETTY_FUNCTION__ << "empty places url that doesn't need setup?"; } } else { KFileItem file = index.data( KDirModel::FileItemRole ).value(); if( file.isDir() ) { d->backStack.push( d->currentPath ); d->forwardStack.clear(); // navigating resets forward stack setDir( file.url() ); } else warning() << __PRETTY_FUNCTION__ << "called for non-directory"; } } void FileBrowser::addItemActivated( const QString &callbackString ) { if( callbackString.isEmpty() ) return; - KUrl newPath; + QUrl newPath; // we have been called with a places name, it means that we'll probably have to mount // the place if( callbackString.startsWith( placesString ) ) { QString name = callbackString.mid( placesString.length() ); for( int i = 0; i < d->placesModel->rowCount(); i++ ) { QModelIndex idx = d->placesModel->index( i, 0 ); if( idx.data().toString() == name ) { if( idx.data( KFilePlacesModel::SetupNeededRole ).toBool() ) { d->bottomPlacesModel->requestSetup( d->placesModel->mapToSource( idx ) ); return; } newPath = idx.data( KFilePlacesModel::UrlRole ).toString(); break; } } if( newPath.isEmpty() ) { warning() << __PRETTY_FUNCTION__ << "name" << name << "not found under Places"; return; } } else newPath = callbackString; d->backStack.push( d->currentPath ); d->forwardStack.clear(); // navigating resets forward stack - setDir( KUrl( newPath ) ); + setDir( QUrl( newPath ) ); } void FileBrowser::setupAddItems() { clearAdditionalItems(); if( d->currentPath == placesUrl ) return; // no more items to add - QString workingUrl = d->currentPath.prettyUrl( KUrl::RemoveTrailingSlash ); + QString workingUrl = d->currentPath.prettyUrl( QUrl::RemoveTrailingSlash ); int currentPosition = 0; QString name; QString callback; BreadcrumbSiblingList siblings; // find QModelIndex of the NON-HIDDEN closestItem QModelIndex placesIndex; - KUrl tempUrl = d->currentPath; + QUrl tempUrl = d->currentPath; do { placesIndex = d->bottomPlacesModel->closestItem( tempUrl ); if( !placesIndex.isValid() ) break; // no valid index even in the bottom model placesIndex = d->placesModel->mapFromSource( placesIndex ); if( placesIndex.isValid() ) break; // found shown placesindex, good! if( tempUrl.upUrl() == tempUrl ) break; // prevent infinite loop tempUrl = tempUrl.upUrl(); } while( true ); // special handling for the first additional item if( placesIndex.isValid() ) { name = placesIndex.data( Qt::DisplayRole ).toString(); callback = placesIndex.data( KFilePlacesModel::UrlRole ).toString(); - KUrl currPlaceUrl = d->placesModel->data( placesIndex, KFilePlacesModel::UrlRole ).toUrl(); - currentPosition = currPlaceUrl.url( KUrl::AddTrailingSlash ).length(); + QUrl currPlaceUrl = d->placesModel->data( placesIndex, KFilePlacesModel::UrlRole ).toUrl(); + currentPosition = currPlaceUrl.url( QUrl::AddTrailingSlash ).length(); } else { QRegExp threeSlashes( "^[^/]*/[^/]*/[^/]*/" ); if( workingUrl.indexOf( threeSlashes ) == 0 ) currentPosition = threeSlashes.matchedLength(); else currentPosition = workingUrl.length(); callback = workingUrl.left( currentPosition ); name = callback; if( name == "file:///" ) name = '/'; // just niceness else name.remove( QRegExp( "/$" ) ); } /* always provide siblings for places, regardless of what first item is; this also - * work-arounds bug 312639, where creating KUrl with accented chars crashes */ + * work-arounds bug 312639, where creating QUrl with accented chars crashes */ siblings = d->siblingsForDir( placesUrl ); addAdditionalItem( new BrowserBreadcrumbItem( name, callback, siblings, this ) ); // other additional items while( !workingUrl.midRef( currentPosition ).isEmpty() ) { int nextPosition = workingUrl.indexOf( '/', currentPosition ) + 1; if( nextPosition <= 0 ) nextPosition = workingUrl.length(); name = workingUrl.mid( currentPosition, nextPosition - currentPosition ); name.remove( QRegExp( "/$" ) ); callback = workingUrl.left( nextPosition ); siblings = d->siblingsForDir( callback ); addAdditionalItem( new BrowserBreadcrumbItem( name, callback, siblings, this ) ); currentPosition = nextPosition; } if( parentList() ) parentList()->childViewChanged(); // emits viewChanged() which causes breadCrumb update } void FileBrowser::reActivate() { d->backStack.push( d->currentPath ); d->forwardStack.clear(); // navigating resets forward stack setDir( placesUrl ); } void -FileBrowser::setDir( const KUrl &dir ) +FileBrowser::setDir( const QUrl &dir ) { if( dir == placesUrl ) { if( d->currentPath != placesUrl ) { d->saveHeaderState(); d->fileView->setModel( d->placesModel ); d->fileView->setSelectionMode( QAbstractItemView::SingleSelection ); d->fileView->header()->setVisible( false ); d->fileView->setDragEnabled( false ); } } else { // if we are currently showing "places" we need to remember to change the model // back to the regular file model if( d->currentPath == placesUrl ) { d->fileView->setModel( d->mimeFilterProxyModel ); d->fileView->setSelectionMode( QAbstractItemView::ExtendedSelection ); d->fileView->setDragEnabled( true ); d->fileView->header()->setVisible( true ); } d->kdirModel->dirLister()->openUrl( dir ); } d->currentPath = dir; d->updateNavigateActions(); setupAddItems(); // set the first item as current so that keyboard navigation works new DelayedActivator( d->fileView ); } void FileBrowser::back() { if( d->backStack.isEmpty() ) return; d->forwardStack.push( d->currentPath ); setDir( d->backStack.pop() ); } void FileBrowser::forward() { if( d->forwardStack.isEmpty() ) return; d->backStack.push( d->currentPath ); // no clearing forward stack here! setDir( d->forwardStack.pop() ); } void FileBrowser::up() { if( d->currentPath == placesUrl ) return; // nothing to do, we consider places as the root view - KUrl upUrl = d->currentPath.upUrl(); + QUrl upUrl = d->currentPath.upUrl(); if( upUrl == d->currentPath ) // apparently, we cannot go up withn url upUrl = placesUrl; d->backStack.push( d->currentPath ); d->forwardStack.clear(); // navigating resets forward stack setDir( upUrl ); } void FileBrowser::home() { d->backStack.push( d->currentPath ); d->forwardStack.clear(); // navigating resets forward stack - setDir( KUrl( QDir::homePath() ) ); + setDir( QUrl( QDir::homePath() ) ); } void FileBrowser::refresh() { setDir( d->currentPath ); } void FileBrowser::setupDone( const QModelIndex &index, bool success ) { if( success ) { QString url = index.data( KFilePlacesModel::UrlRole ).value(); if( !url.isEmpty() ) { d->backStack.push( d->currentPath ); d->forwardStack.clear(); // navigating resets forward stack setDir( url ); } } } DelayedActivator::DelayedActivator( QAbstractItemView *view ) : QObject( view ) , m_view( view ) { QAbstractItemModel *model = view->model(); if( !model ) { deleteLater(); return; } // short-cut for already-filled models if( model->rowCount() > 0 ) { slotRowsInserted( QModelIndex(), 0 ); return; } connect( model, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(slotRowsInserted(QModelIndex,int)) ); connect( model, SIGNAL(destroyed(QObject*)), SLOT(deleteLater()) ); connect( model, SIGNAL(layoutChanged()), SLOT(deleteLater()) ); connect( model, SIGNAL(modelReset()), SLOT(deleteLater()) ); } void DelayedActivator::slotRowsInserted( const QModelIndex &parent, int start ) { QAbstractItemModel *model = m_view->model(); if( model ) { // prevent duplicate calls, deleteLater() may fire REAL later disconnect( model, 0, this, 0 ); QModelIndex idx = model->index( start, 0, parent ); m_view->selectionModel()->setCurrentIndex( idx, QItemSelectionModel::NoUpdate ); } deleteLater(); } diff --git a/src/browsers/filebrowser/FileBrowser.h b/src/browsers/filebrowser/FileBrowser.h index 9870154dea..5f76d1e172 100644 --- a/src/browsers/filebrowser/FileBrowser.h +++ b/src/browsers/filebrowser/FileBrowser.h @@ -1,122 +1,122 @@ /**************************************************************************************** * Copyright (c) 2010 Nikolaj Hald Nielsen * * * * 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, see . * ****************************************************************************************/ #ifndef FILEBROWSERMKII_H #define FILEBROWSERMKII_H #include "browsers/BrowserCategory.h" -#include +#include class QAbstractItemView; class QModelIndex; class FileBrowser : public BrowserCategory { Q_OBJECT public: FileBrowser( const char *name, QWidget *parent ); ~FileBrowser(); virtual void setupAddItems(); /** * Navigate to a specific directory */ - void setDir( const KUrl &dir ); + void setDir( const QUrl &dir ); /** * Return the path of the currently shown dir. */ QString currentDir() const; protected slots: void slotNavigateToDirectory( const QModelIndex &index ); void addItemActivated( const QString &callback ); virtual void reActivate(); /** * Shows/hides the columns as selected in the context menu of the header of the * file view. * @param toggled the visibility state of a column in the context menu. */ void toggleColumn( bool toggled ); /** * Go backward in history */ void back(); /** * Go forward in history */ void forward(); /** * Navigates up one level in the path shown */ void up(); /** * Navigates to home directory */ void home(); /* * Refreshes current directory */ void refresh(); /** * Handle results of tryiong to setup an item in "places" that needed mouting or other * special setup. * @param index the index that we tried to setup * @param success did the setup succeed? */ void setupDone( const QModelIndex &index, bool success ); private slots: void initView(); private: class Private; Private *const d; Q_PRIVATE_SLOT( d, void updateHeaderState() ) }; /** * Helper class that calls setCurrentIndex on a model view as soon as the model * adds a row and then it auto-deletes itself. */ class DelayedActivator : public QObject { Q_OBJECT public: explicit DelayedActivator( QAbstractItemView *view ); private slots: void slotRowsInserted( const QModelIndex &parent, int start ); private: QAbstractItemView *m_view; }; #endif // FILEBROWSERMKII_H diff --git a/src/browsers/filebrowser/FileBrowser_p.h b/src/browsers/filebrowser/FileBrowser_p.h index b5545cf6f5..c7223538ca 100644 --- a/src/browsers/filebrowser/FileBrowser_p.h +++ b/src/browsers/filebrowser/FileBrowser_p.h @@ -1,162 +1,162 @@ /**************************************************************************************** * Copyright (c) 2010 Nikolaj Hald Nielsen * * Copyright (c) 2010 Casey Link * * Copyright (c) 2010 Téo Mrnjavac * * Copyright (c) 2010 Rick W. Chen * * * * 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, see . * ****************************************************************************************/ #ifndef AMAROK_FILEBROWSER_P_H #define AMAROK_FILEBROWSER_P_H #include "FileBrowser.h" #include "browsers/BrowserBreadcrumbItem.h" #include #include #include #include #include class QSortFilterProxyModel; class DirBrowserModel; class SearchWidget; class FileView; class KAction; class KFilePlacesModel; class DirPlaylistTrackFilterProxyModel; template class UniqueStack : public QStack { public: inline void push( const T &t ) { if( QStack::isEmpty() || t != QStack::top() ) QStack::push( t ); } }; class FileBrowser::Private { public: Private( FileBrowser *parent ); ~Private(); void readConfig(); void writeConfig(); void restoreHeaderState(); void saveHeaderState(); void updateNavigateActions(); - BreadcrumbSiblingList siblingsForDir( const KUrl &path ); + BreadcrumbSiblingList siblingsForDir( const QUrl &path ); void updateHeaderState(); QList columnActions; //!< Maintains the mapping action<->column KFilePlacesModel *bottomPlacesModel; QSortFilterProxyModel *placesModel; DirBrowserModel *kdirModel; DirPlaylistTrackFilterProxyModel *mimeFilterProxyModel; SearchWidget *searchWidget; - KUrl currentPath; + QUrl currentPath; FileView *fileView; KAction *upAction; KAction *homeAction; KAction *refreshAction; KAction *backAction; KAction *forwardAction; - UniqueStack backStack; - UniqueStack forwardStack; + UniqueStack backStack; + UniqueStack forwardStack; private: void restoreDefaultHeaderState(); FileBrowser *const q; }; class DirBrowserModel : public KDirModel { Q_OBJECT public: DirBrowserModel( QObject *parent = 0 ) : KDirModel( parent ) { updateRowHeight(); connect( KGlobalSettings::self(), SIGNAL(appearanceChanged()), SLOT(updateRowHeight()) ); } virtual ~DirBrowserModel() {} virtual QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const { if( role == Qt::SizeHintRole ) return QSize( 1, rowHeight ); else return KDirModel::data( index, role ); } private slots: void updateRowHeight() { QFont font; rowHeight = QFontMetrics( font ).height() + 4; } private: int rowHeight; }; class FilePlacesModel : public KFilePlacesModel { Q_OBJECT public: FilePlacesModel( QObject *parent = 0 ) : KFilePlacesModel( parent ) { updateRowHeight(); connect( KGlobalSettings::self(), SIGNAL(appearanceChanged()), SLOT(updateRowHeight()) ); } virtual ~FilePlacesModel() {} virtual QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const { if( role == Qt::SizeHintRole ) return QSize( 1, rowHeight ); else return KFilePlacesModel::data( index, role ); } private slots: void updateRowHeight() { QFont font; rowHeight = QFontMetrics( font ).height() + 4; } private: int rowHeight; }; #endif /* AMAROK_FILEBROWSER_P_H */ diff --git a/src/browsers/filebrowser/FileView.cpp b/src/browsers/filebrowser/FileView.cpp index ad200ebd4b..3f9729559b 100644 --- a/src/browsers/filebrowser/FileView.cpp +++ b/src/browsers/filebrowser/FileView.cpp @@ -1,613 +1,613 @@ /**************************************************************************************** * Copyright (c) 2010 Nikolaj Hald Nielsen * * Copyright (c) 2010 Casey Link * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "FileView" #include "FileView.h" #include "EngineController.h" #include "PaletteHandler.h" #include "PopupDropperFactory.h" #include "SvgHandler.h" #include "context/ContextView.h" #include "context/popupdropper/libpud/PopupDropper.h" #include "context/popupdropper/libpud/PopupDropperItem.h" #include "core/playlists/PlaylistFormat.h" #include "core/support/Debug.h" #include "core-impl/collections/support/CollectionManager.h" #include "core-impl/collections/support/FileCollectionLocation.h" #include "core-impl/meta/file/File.h" #include "core-impl/playlists/types/file/PlaylistFileSupport.h" #include "core-impl/support/TrackLoader.h" #include "dialogs/TagDialog.h" #include #include #include #include #include #include #include #include #include #include #include -#include +#include #include #include #include #include FileView::FileView( QWidget *parent ) : Amarok::PrettyTreeView( parent ) , m_appendAction( 0 ) , m_loadAction( 0 ) , m_editAction( 0 ) , m_moveToTrashAction( 0 ) , m_deleteAction( 0 ) , m_pd( 0 ) , m_ongoingDrag( false ) { setFrameStyle( QFrame::NoFrame ); setItemsExpandable( false ); setRootIsDecorated( false ); setAlternatingRowColors( true ); setUniformRowHeights( true ); setEditTriggers( EditKeyPressed ); The::paletteHandler()->updateItemView( this ); connect( The::paletteHandler(), SIGNAL(newPalette(QPalette)), SLOT(newPalette(QPalette)) ); } void FileView::contextMenuEvent( QContextMenuEvent *e ) { if( !model() ) return; //trying to do fancy stuff while showing places only leads to tears! if( model()->objectName() == "PLACESMODEL" ) { e->accept(); return; } QModelIndexList indices = selectedIndexes(); // Abort if nothing is selected if( indices.isEmpty() ) return; KMenu menu; foreach( QAction *action, actionsForIndices( indices, PlaylistAction ) ) menu.addAction( action ); menu.addSeparator(); // Create Copy/Move to menu items // ported from old filebrowser QList writableCollections; QHash hash = CollectionManager::instance()->collections(); QHash::const_iterator it = hash.constBegin(); while( it != hash.constEnd() ) { Collections::Collection *coll = it.key(); if( coll && coll->isWritable() ) writableCollections.append( coll ); ++it; } if( !writableCollections.isEmpty() ) { QMenu *copyMenu = new QMenu( i18n( "Copy to Collection" ), &menu ); copyMenu->setIcon( KIcon( "edit-copy" ) ); foreach( Collections::Collection *coll, writableCollections ) { CollectionAction *copyAction = new CollectionAction( coll, &menu ); connect( copyAction, SIGNAL(triggered()), this, SLOT(slotPrepareCopyTracks()) ); copyMenu->addAction( copyAction ); } menu.addMenu( copyMenu ); QMenu *moveMenu = new QMenu( i18n( "Move to Collection" ), &menu ); moveMenu->setIcon( KIcon( "go-jump" ) ); foreach( Collections::Collection *coll, writableCollections ) { CollectionAction *moveAction = new CollectionAction( coll, &menu ); connect( moveAction, SIGNAL(triggered()), this, SLOT(slotPrepareMoveTracks()) ); moveMenu->addAction( moveAction ); } menu.addMenu( moveMenu ); } foreach( QAction *action, actionsForIndices( indices, OrganizeAction ) ) menu.addAction( action ); menu.addSeparator(); foreach( QAction *action, actionsForIndices( indices, EditAction ) ) menu.addAction( action ); menu.exec( e->globalPos() ); } void FileView::mouseReleaseEvent( QMouseEvent *event ) { QModelIndex index = indexAt( event->pos() ); if( !index.isValid() ) { PrettyTreeView::mouseReleaseEvent( event ); return; } if( state() == QAbstractItemView::NoState && event->button() == Qt::MidButton ) { addIndexToPlaylist( index, Playlist::OnMiddleClickOnSelectedItems ); event->accept(); return; } KFileItem file = index.data( KDirModel::FileItemRole ).value(); if( state() == QAbstractItemView::NoState && event->button() == Qt::LeftButton && event->modifiers() == Qt::NoModifier && KGlobalSettings::singleClick() && ( file.isDir() || file.isNull() ) ) { emit navigateToDirectory( index ); event->accept(); return; } PrettyTreeView::mouseReleaseEvent( event ); } void FileView::mouseDoubleClickEvent( QMouseEvent *event ) { QModelIndex index = indexAt( event->pos() ); if( !index.isValid() ) { event->accept(); return; } // swallow middle-button double-clicks if( event->button() == Qt::MidButton ) { event->accept(); return; } if( event->button() == Qt::LeftButton ) { KFileItem file = index.data( KDirModel::FileItemRole ).value(); - KUrl url = file.url(); + QUrl url = file.url(); if( !file.isNull() && ( Playlists::isPlaylist( url ) || MetaFile::Track::isTrack( url ) ) ) addIndexToPlaylist( index, Playlist::OnDoubleClickOnSelectedItems ); else emit navigateToDirectory( index ); event->accept(); return; } PrettyTreeView::mouseDoubleClickEvent( event ); } void FileView::keyPressEvent( QKeyEvent *event ) { QModelIndex index = currentIndex(); if( !index.isValid() ) return; switch( event->key() ) { case Qt::Key_Enter: case Qt::Key_Return: { KFileItem file = index.data( KDirModel::FileItemRole ).value(); - KUrl url = file.url(); + QUrl url = file.url(); if( !file.isNull() && ( Playlists::isPlaylist( url ) || MetaFile::Track::isTrack( url ) ) ) // right, we test the current item, but then add the selection to playlist addSelectionToPlaylist( Playlist::OnReturnPressedOnSelectedItems ); else emit navigateToDirectory( index ); return; } case Qt::Key_Delete: slotMoveToTrash( Qt::NoButton, event->modifiers() ); break; case Qt::Key_F5: emit refreshBrowser(); break; default: break; } QTreeView::keyPressEvent( event ); } void FileView::slotAppendToPlaylist() { addSelectionToPlaylist( Playlist::OnAppendToPlaylistAction ); } void FileView::slotReplacePlaylist() { addSelectionToPlaylist( Playlist::OnReplacePlaylistAction ); } void FileView::slotEditTracks() { Meta::TrackList tracks = tracksForEdit(); if( !tracks.isEmpty() ) { TagDialog *dialog = new TagDialog( tracks, this ); dialog->show(); } } void FileView::slotPrepareMoveTracks() { if( m_moveDestinationCollection ) return; CollectionAction *action = dynamic_cast( sender() ); if( !action ) return; m_moveDestinationCollection = action->collection(); const KFileItemList list = selectedItems(); if( list.isEmpty() ) return; // prevent bug 313003, require full metadata TrackLoader* dl = new TrackLoader( TrackLoader::FullMetadataRequired ); // auto-deletes itself connect( dl, SIGNAL(finished(Meta::TrackList)), SLOT(slotMoveTracks(Meta::TrackList)) ); dl->init( list.urlList() ); } void FileView::slotPrepareCopyTracks() { if( m_copyDestinationCollection ) return; CollectionAction *action = dynamic_cast( sender() ); if( !action ) return; m_copyDestinationCollection = action->collection(); const KFileItemList list = selectedItems(); if( list.isEmpty() ) return; // prevent bug 313003, require full metadata TrackLoader* dl = new TrackLoader( TrackLoader::FullMetadataRequired ); // auto-deletes itself connect( dl, SIGNAL(finished(Meta::TrackList)), SLOT(slotCopyTracks(Meta::TrackList)) ); dl->init( list.urlList() ); } void FileView::slotCopyTracks( const Meta::TrackList& tracks ) { if( !m_copyDestinationCollection ) return; QSet collections; foreach( const Meta::TrackPtr &track, tracks ) { collections.insert( track->collection() ); } if( collections.count() == 1 ) { Collections::Collection *sourceCollection = collections.values().first(); Collections::CollectionLocation *source; if( sourceCollection ) source = sourceCollection->location(); else source = new Collections::FileCollectionLocation(); Collections::CollectionLocation *destination = m_copyDestinationCollection.data()->location(); source->prepareCopy( tracks, destination ); } else warning() << "Cannot handle copying tracks from multiple collections, doing nothing to be safe"; m_copyDestinationCollection.clear(); } void FileView::slotMoveTracks( const Meta::TrackList& tracks ) { if( !m_moveDestinationCollection ) return; QSet collections; foreach( const Meta::TrackPtr &track, tracks ) { collections.insert( track->collection() ); } if( collections.count() == 1 ) { Collections::Collection *sourceCollection = collections.values().first(); Collections::CollectionLocation *source; if( sourceCollection ) source = sourceCollection->location(); else source = new Collections::FileCollectionLocation(); Collections::CollectionLocation *destination = m_moveDestinationCollection.data()->location(); source->prepareMove( tracks, destination ); } else warning() << "Cannot handle moving tracks from multiple collections, doing nothing to be safe"; m_moveDestinationCollection.clear(); } QList FileView::actionsForIndices( const QModelIndexList &indices, ActionType type ) { QList actions; if( indices.isEmpty() ) return actions; // get out of here! if( !m_appendAction ) { m_appendAction = new QAction( KIcon( "media-track-add-amarok" ), i18n( "&Add to Playlist" ), this ); m_appendAction->setProperty( "popupdropper_svg_id", "append" ); connect( m_appendAction, SIGNAL(triggered()), this, SLOT(slotAppendToPlaylist()) ); } if( type & PlaylistAction ) actions.append( m_appendAction ); if( !m_loadAction ) { m_loadAction = new QAction( i18nc( "Replace the currently loaded tracks with these", "&Replace Playlist" ), this ); m_loadAction->setProperty( "popupdropper_svg_id", "load" ); connect( m_loadAction, SIGNAL(triggered()), this, SLOT(slotReplacePlaylist()) ); } if( type & PlaylistAction ) actions.append( m_loadAction ); if( !m_moveToTrashAction ) { m_moveToTrashAction = new KAction( KIcon( "user-trash" ), i18n( "&Move to Trash" ), this ); m_moveToTrashAction->setProperty( "popupdropper_svg_id", "delete_file" ); // key shortcut is only for display purposes here, actual one is determined by View in Model/View classes m_moveToTrashAction->setShortcut( Qt::Key_Delete ); connect( m_moveToTrashAction, SIGNAL(triggered(Qt::MouseButtons,Qt::KeyboardModifiers)), this, SLOT(slotMoveToTrash(Qt::MouseButtons,Qt::KeyboardModifiers)) ); } if( type & OrganizeAction ) actions.append( m_moveToTrashAction ); if( !m_deleteAction ) { m_deleteAction = new KAction( KIcon( "remove-amarok" ), i18n( "&Delete" ), this ); m_deleteAction->setProperty( "popupdropper_svg_id", "delete_file" ); // key shortcut is only for display purposes here, actual one is determined by View in Model/View classes m_deleteAction->setShortcut( Qt::SHIFT + Qt::Key_Delete ); connect( m_deleteAction, SIGNAL(triggered(bool)), SLOT(slotDelete()) ); } if( type & OrganizeAction ) actions.append( m_deleteAction ); if( !m_editAction ) { m_editAction = new QAction( KIcon( "media-track-edit-amarok" ), i18n( "&Edit Track Details" ), this ); m_editAction->setProperty( "popupdropper_svg_id", "edit" ); connect( m_editAction, SIGNAL(triggered()), this, SLOT(slotEditTracks()) ); } if( type & EditAction ) { actions.append( m_editAction ); Meta::TrackList tracks = tracksForEdit(); m_editAction->setVisible( !tracks.isEmpty() ); } return actions; } void FileView::addSelectionToPlaylist( Playlist::AddOptions options ) { addIndicesToPlaylist( selectedIndexes(), options ); } void FileView::addIndexToPlaylist( const QModelIndex &idx, Playlist::AddOptions options ) { addIndicesToPlaylist( QModelIndexList() << idx, options ); } void FileView::addIndicesToPlaylist( QModelIndexList indices, Playlist::AddOptions options ) { if( indices.isEmpty() ) return; // let tracks & playlists appear in playlist as they are shown in the view: qSort( indices ); - QList urls; + QList urls; foreach( const QModelIndex &index, indices ) { KFileItem file = index.data( KDirModel::FileItemRole ).value(); - KUrl url = file.url(); + QUrl url = file.url(); if( file.isDir() || Playlists::isPlaylist( url ) || MetaFile::Track::isTrack( url ) ) { urls << file.url(); } } The::playlistController()->insertOptioned( urls, options ); } void FileView::startDrag( Qt::DropActions supportedActions ) { //setSelectionMode( QAbstractItemView::NoSelection ); // When a parent item is dragged, startDrag() is called a bunch of times. Here we prevent that: m_dragMutex.lock(); if( m_ongoingDrag ) { m_dragMutex.unlock(); return; } m_ongoingDrag = true; m_dragMutex.unlock(); if( !m_pd ) m_pd = The::popupDropperFactory()->createPopupDropper( Context::ContextView::self() ); if( m_pd && m_pd->isHidden() ) { QModelIndexList indices = selectedIndexes(); QList actions = actionsForIndices( indices ); QFont font; font.setPointSize( 16 ); font.setBold( true ); foreach( QAction *action, actions ) m_pd->addItem( The::popupDropperFactory()->createItem( action ) ); m_pd->show(); } QTreeView::startDrag( supportedActions ); if( m_pd ) { connect( m_pd, SIGNAL(fadeHideFinished()), m_pd, SLOT(clear()) ); m_pd->hide(); } m_dragMutex.lock(); m_ongoingDrag = false; m_dragMutex.unlock(); } KFileItemList FileView::selectedItems() const { KFileItemList items; QModelIndexList indices = selectedIndexes(); if( indices.isEmpty() ) return items; foreach( const QModelIndex& index, indices ) { KFileItem item = index.data( KDirModel::FileItemRole ).value(); items << item; } return items; } Meta::TrackList FileView::tracksForEdit() const { Meta::TrackList tracks; QModelIndexList indices = selectedIndexes(); if( indices.isEmpty() ) return tracks; foreach( const QModelIndex &index, indices ) { KFileItem item = index.data( KDirModel::FileItemRole ).value(); Meta::TrackPtr track = CollectionManager::instance()->trackForUrl( item.url() ); if( track ) tracks << track; } return tracks; } void FileView::slotMoveToTrash( Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers ) { Q_UNUSED( buttons ) DEBUG_BLOCK QModelIndexList indices = selectedIndexes(); if( indices.isEmpty() ) return; const bool deleting = modifiers.testFlag( Qt::ShiftModifier ); QString caption; QString labelText; if( deleting ) { caption = i18nc( "@title:window", "Confirm Delete" ); labelText = i18np( "Are you sure you want to delete this item?", "Are you sure you want to delete these %1 items?", indices.count() ); } else { caption = i18nc( "@title:window", "Confirm Move to Trash" ); labelText = i18np( "Are you sure you want to move this item to trash?", "Are you sure you want to move these %1 items to trash?", indices.count() ); } - KUrl::List urls; + QList urls; QStringList filepaths; foreach( const QModelIndex& index, indices ) { KFileItem file = index.data( KDirModel::FileItemRole ).value(); filepaths << file.localPath(); urls << file.url(); } KGuiItem confirmButton = deleting ? KStandardGuiItem::del() : KStandardGuiItem::remove(); if( KMessageBox::warningContinueCancelList( this, labelText, filepaths, caption, confirmButton ) != KMessageBox::Continue ) return; if( deleting ) { KIO::del( urls, KIO::HideProgressInfo ); return; } KIO::trash( urls, KIO::HideProgressInfo ); } void FileView::slotDelete() { slotMoveToTrash( Qt::NoButton, Qt::ShiftModifier ); } diff --git a/src/browsers/playlistbrowser/PlaylistBrowserView.cpp b/src/browsers/playlistbrowser/PlaylistBrowserView.cpp index bb2f4883df..cb326aca2f 100644 --- a/src/browsers/playlistbrowser/PlaylistBrowserView.cpp +++ b/src/browsers/playlistbrowser/PlaylistBrowserView.cpp @@ -1,583 +1,583 @@ /**************************************************************************************** * Copyright (c) 2008 Nikolaj Hald Nielsen * * Copyright (c) 2010 Bart Cerneels * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "PlaylistBrowserView" #include "PlaylistBrowserView.h" #include "PaletteHandler.h" #include "PopupDropperFactory.h" #include "SvgHandler.h" #include "amarokconfig.h" #include "browsers/playlistbrowser/PlaylistBrowserModel.h" #include "browsers/playlistbrowser/PlaylistsByProviderProxy.h" #include "browsers/playlistbrowser/PlaylistsInFoldersProxy.h" #include "context/ContextView.h" #include "context/popupdropper/libpud/PopupDropperItem.h" #include "context/popupdropper/libpud/PopupDropper.h" #include "core/support/Debug.h" #include "core-impl/playlists/types/file/PlaylistFileSupport.h" #include "playlist/PlaylistModel.h" #include "playlistmanager/PlaylistManager.h" #include "widgets/PrettyTreeRoles.h" #include #include #include #include #include #include #include Q_DECLARE_METATYPE( QModelIndexList ) using namespace PlaylistBrowserNS; PlaylistBrowserNS::PlaylistBrowserView::PlaylistBrowserView( QAbstractItemModel *model, QWidget *parent ) : Amarok::PrettyTreeView( parent ) , m_pd( 0 ) , m_ongoingDrag( false ) { DEBUG_BLOCK setModel( model ); setSelectionMode( QAbstractItemView::ExtendedSelection ); setSelectionBehavior( QAbstractItemView::SelectItems ); setDragDropMode( QAbstractItemView::DragDrop ); setAcceptDrops( true ); setEditTriggers( QAbstractItemView::EditKeyPressed ); setMouseTracking( true ); // needed for highlighting provider action icons m_createEmptyPlaylistAction = new QAction( KIcon( "media-track-add-amarok" ), i18n( "Create an Empty Playlist" ), this ); connect( m_createEmptyPlaylistAction, SIGNAL(triggered()), SLOT(slotCreateEmptyPlaylist()) ); m_appendAction = new QAction( KIcon( "media-track-add-amarok" ), i18n( "&Add to Playlist" ), this ); m_appendAction->setProperty( "popupdropper_svg_id", "append" ); connect( m_appendAction, SIGNAL(triggered()), this, SLOT(slotAppend()) ); m_loadAction = new QAction( KIcon( "folder-open" ), i18nc( "Replace the currently " "loaded tracks with these", "&Replace Playlist" ), this ); m_loadAction->setProperty( "popupdropper_svg_id", "load" ); connect( m_loadAction, SIGNAL(triggered()), this, SLOT(slotLoad()) ); m_setNewAction = new QAction( KIcon( "rating" ), i18nc( "toggle the \"new\" status " " of this podcast episode", "&New" ), this ); m_setNewAction->setProperty( "popupdropper_svg_id", "new" ); m_setNewAction->setCheckable( true ); connect( m_setNewAction, SIGNAL(triggered(bool)), SLOT(slotSetNew(bool)) ); m_renamePlaylistAction = new QAction( KIcon( "media-track-edit-amarok" ), i18n( "&Rename..." ), this ); m_renamePlaylistAction->setProperty( "popupdropper_svg_id", "edit" ); // key shortcut is only for display purposes here, actual one is determined by View in Model/View classes m_renamePlaylistAction->setShortcut( Qt::Key_F2 ); connect( m_renamePlaylistAction, SIGNAL(triggered()), this, SLOT(slotRename()) ); m_deletePlaylistAction = new QAction( KIcon( "media-track-remove-amarok" ), i18n( "&Delete..." ), this ); m_deletePlaylistAction->setProperty( "popupdropper_svg_id", "delete" ); // key shortcut is only for display purposes here, actual one is determined by View in Model/View classes m_deletePlaylistAction->setShortcut( Qt::Key_Delete ); connect( m_deletePlaylistAction, SIGNAL(triggered()), SLOT(slotDelete()) ); m_removeTracksAction = new QAction( KIcon( "media-track-remove-amarok" ), QString( "" ), this ); m_removeTracksAction->setProperty( "popupdropper_svg_id", "delete" ); // key shortcut is only for display purposes here, actual one is determined by View in Model/View classes m_removeTracksAction->setShortcut( Qt::Key_Delete ); connect( m_removeTracksAction, SIGNAL(triggered()), SLOT(slotRemoveTracks()) ); m_exportAction = new QAction( KIcon( "document-export-amarok" ), i18n( "&Export As..." ), this ); connect( m_exportAction, SIGNAL(triggered()), this, SLOT(slotExport()) ); m_separatorAction = new QAction( this ); m_separatorAction->setSeparator( true ); } void PlaylistBrowserNS::PlaylistBrowserView::setModel( QAbstractItemModel *model ) { if( this->model() ) disconnect( this->model(), 0, this, 0 ); Amarok::PrettyTreeView::setModel( model ); connect( this->model(), SIGNAL(renameIndex(QModelIndex)), SLOT(edit(QModelIndex)) ); } void PlaylistBrowserNS::PlaylistBrowserView::mouseReleaseEvent( QMouseEvent *event ) { if( m_pd ) { connect( m_pd, SIGNAL(fadeHideFinished()), m_pd, SLOT(deleteLater()) ); m_pd->hide(); m_pd = 0; } QModelIndex index = indexAt( event->pos() ); if( !index.isValid() ) { PrettyTreeView::mouseReleaseEvent( event ); return; } if( event->button() == Qt::MidButton ) { insertIntoPlaylist( index, Playlist::OnMiddleClickOnSelectedItems ); event->accept(); return; } PrettyTreeView::mouseReleaseEvent( event ); } void PlaylistBrowserNS::PlaylistBrowserView::startDrag( Qt::DropActions supportedActions ) { // Waah? when a parent item is dragged, startDrag is called a bunch of times if( m_ongoingDrag ) return; m_ongoingDrag = true; if( !m_pd ) m_pd = The::popupDropperFactory()->createPopupDropper( Context::ContextView::self() ); if( m_pd && m_pd->isHidden() ) { QActionList actions = actionsFor( selectedIndexes() ); foreach( QAction *action, actions ) m_pd->addItem( The::popupDropperFactory()->createItem( action ) ); m_pd->show(); } QTreeView::startDrag( supportedActions ); // We keep the items that the actions need to be applied to. // Clear the data from all actions now that the PUD has executed. resetActionTargets(); if( m_pd ) { connect( m_pd, SIGNAL(fadeHideFinished()), m_pd, SLOT(clear()) ); m_pd->hide(); } m_ongoingDrag = false; } void PlaylistBrowserNS::PlaylistBrowserView::keyPressEvent( QKeyEvent *event ) { QModelIndexList indices = selectedIndexes(); // mind bug 305203 if( indices.isEmpty() || state() != QAbstractItemView::NoState ) { Amarok::PrettyTreeView::keyPressEvent( event ); return; } switch( event->key() ) { //activated() only works for current index, not all selected case Qt::Key_Enter: case Qt::Key_Return: insertIntoPlaylist( indices, Playlist::OnReturnPressedOnSelectedItems ); return; case Qt::Key_Delete: { QActionList actions = actionsFor( indices ); // sets action targets if( actions.contains( m_removeTracksAction ) ) m_removeTracksAction->trigger(); else if( actions.contains( m_deletePlaylistAction ) ) m_deletePlaylistAction->trigger(); resetActionTargets(); return; } default: break; } Amarok::PrettyTreeView::keyPressEvent( event ); } void PlaylistBrowserNS::PlaylistBrowserView::mouseDoubleClickEvent( QMouseEvent *event ) { if( event->button() == Qt::MidButton ) { event->accept(); return; } QModelIndex index = indexAt( event->pos() ); if( !index.isValid() ) { event->accept(); return; } // code copied in CollectionTreeView::mouseDoubleClickEvent(), keep in sync // mind bug 279513 bool isExpandable = model()->hasChildren( index ); bool wouldExpand = !visualRect( index ).contains( event->pos() ) || // clicked outside item, perhaps on expander icon ( isExpandable && !KGlobalSettings::singleClick() ); // we're in doubleClick if( event->button() == Qt::LeftButton && event->modifiers() == Qt::NoModifier && !wouldExpand ) { insertIntoPlaylist( index, Playlist::OnDoubleClickOnSelectedItems ); event->accept(); return; } PrettyTreeView::mouseDoubleClickEvent( event ); } void PlaylistBrowserNS::PlaylistBrowserView::contextMenuEvent( QContextMenuEvent *event ) { QModelIndex clickedIdx = indexAt( event->pos() ); QModelIndexList indices; if( clickedIdx.isValid() && selectedIndexes().contains( clickedIdx ) ) indices << selectedIndexes(); else if( clickedIdx.isValid() ) indices << clickedIdx; QActionList actions = actionsFor( indices ); if( actions.isEmpty() ) { resetActionTargets(); return; } KMenu menu; foreach( QAction *action, actions ) menu.addAction( action ); menu.exec( mapToGlobal( event->pos() ) ); // We keep the items that the action need to be applied to. // Clear the data from all actions now that the context menu has executed. resetActionTargets(); } QList PlaylistBrowserNS::PlaylistBrowserView::actionsFor( const QModelIndexList &indexes ) { resetActionTargets(); if( indexes.isEmpty() ) return QActionList(); using namespace Playlists; QSet providers, writableProviders; QActionList actions; QModelIndexList newPodcastEpisodes, oldPodcastEpisodes; foreach( const QModelIndex &idx, indexes ) { // direct provider actions: actions << idx.data( PrettyTreeRoles::DecoratorRole ).value(); PlaylistProvider *provider = idx.data( PlaylistBrowserModel::ProviderRole ).value(); if( provider ) providers << provider; bool isWritable = provider ? provider->isWritable() : false; if( isWritable ) writableProviders |= provider; Meta::TrackPtr track = idx.data( PlaylistBrowserModel::TrackRole ).value(); PlaylistPtr playlist = idx.data( PlaylistBrowserModel::PlaylistRole ).value(); if( !track && playlist ) // a playlist (must check it is not a track) { m_actionPlaylists << playlist; if( isWritable ) m_writableActionPlaylists << playlist; } if( track ) { m_actionTracks.insert( playlist, idx.row() ); if( isWritable ) m_writableActionTracks.insert( playlist, idx.row() ); } QVariant episodeIsNew = idx.data( PlaylistBrowserModel::EpisodeIsNewRole ); if( episodeIsNew.type() == QVariant::Bool ) { if( episodeIsNew.toBool() ) newPodcastEpisodes << idx; else oldPodcastEpisodes << idx; } } // all actions taking provider have only sense with one provider if( writableProviders.count() == 1 ) m_writableActionProvider = writableProviders.toList().first(); // process per-provider actions foreach( PlaylistProvider *provider, providers ) { // prepare arguments and get relevant actions PlaylistList providerPlaylists; foreach( const PlaylistPtr &playlist, m_actionPlaylists ) { if( playlist->provider() == provider ) providerPlaylists << playlist; } actions << provider->playlistActions( providerPlaylists ); QMultiHash playlistTracks; QHashIterator it( m_actionTracks ); while( it.hasNext() ) { it.next(); if( it.key()->provider() == provider ) playlistTracks.insert( it.key(), it.value() ); } actions << provider->trackActions( playlistTracks ); } // separate model actions from standard actions we provide (at the top) QActionList standardActions; if( m_actionPlaylists.isEmpty() && m_actionTracks.isEmpty() && m_writableActionProvider ) standardActions << m_createEmptyPlaylistAction; if( !m_actionPlaylists.isEmpty() || !m_actionTracks.isEmpty() ) standardActions << m_appendAction << m_loadAction; if( !newPodcastEpisodes.isEmpty() || !oldPodcastEpisodes.isEmpty() ) { m_setNewAction->setChecked( oldPodcastEpisodes.isEmpty() ); m_setNewAction->setData( QVariant::fromValue( newPodcastEpisodes + oldPodcastEpisodes ) ); standardActions << m_setNewAction; } if( m_writableActionPlaylists.count() == 1 && m_actionTracks.isEmpty() ) standardActions << m_renamePlaylistAction; if( !m_writableActionPlaylists.isEmpty() && m_actionTracks.isEmpty() ) standardActions << m_deletePlaylistAction; if( m_actionPlaylists.isEmpty() && !m_writableActionTracks.isEmpty() ) { const int actionTrackCount = m_writableActionTracks.count(); const int playlistCount = m_writableActionTracks.uniqueKeys().count(); if( playlistCount > 1 ) m_removeTracksAction->setText( i18nc( "%1: number of tracks. %2: number of playlists", "Remove %1 From %2", i18ncp ("First part of 'Remove %1 From %2'", "a Track", "%1 Tracks", actionTrackCount), i18ncp ("Second part of 'Remove %1 From %2'", "1 Playlist", "%1 Playlists", playlistCount ) ) ); else m_removeTracksAction->setText( i18ncp( "%2 is saved playlist name", "Remove a Track From %2", "Remove %1 Tracks From %2", actionTrackCount, m_writableActionTracks.uniqueKeys().first()->prettyName() ) ); standardActions << m_removeTracksAction; } if( m_actionPlaylists.count() == 1 && m_actionTracks.isEmpty() ) standardActions << m_exportAction; standardActions << m_separatorAction; return standardActions + actions; } void PlaylistBrowserView::resetActionTargets() { m_writableActionProvider = 0; m_actionPlaylists.clear(); m_writableActionPlaylists.clear(); m_actionTracks.clear(); m_writableActionTracks.clear(); } void PlaylistBrowserNS::PlaylistBrowserView::currentChanged( const QModelIndex ¤t, const QModelIndex &previous ) { Q_UNUSED( previous ) emit currentItemChanged( current ); Amarok::PrettyTreeView::currentChanged( current, previous ); } void PlaylistBrowserView::slotCreateEmptyPlaylist() { // m_actionProvider may be null, which is fine The::playlistManager()->save( Meta::TrackList(), Amarok::generatePlaylistName( Meta::TrackList() ), m_writableActionProvider ); } void PlaylistBrowserView::slotAppend() { insertIntoPlaylist( Playlist::OnAppendToPlaylistAction ); } void PlaylistBrowserView::slotLoad() { insertIntoPlaylist( Playlist::OnReplacePlaylistAction ); } void PlaylistBrowserView::slotSetNew( bool newState ) { QModelIndexList indices = m_setNewAction->data().value(); foreach( const QModelIndex &idx, indices ) model()->setData( idx, newState, PlaylistBrowserModel::EpisodeIsNewRole ); } void PlaylistBrowserView::slotRename() { if( m_writableActionPlaylists.count() != 1 ) { warning() << __PRETTY_FUNCTION__ << "m_writableActionPlaylists.count() is not 1"; return; } Playlists::PlaylistPtr playlist = m_writableActionPlaylists.at( 0 ); // TODO: this makes a rather complicated round-trip and ends up in edit(QModelIndex) // here -- simplify that The::playlistManager()->rename( playlist ); } void PlaylistBrowserView::slotDelete() { if( m_writableActionPlaylists.isEmpty() ) return; using namespace Playlists; QHash providerPlaylists; foreach( const PlaylistPtr &playlist, m_writableActionPlaylists ) { if( playlist->provider() ) providerPlaylists[ playlist->provider() ] << playlist; } QStringList providerNames; foreach( const PlaylistProvider *provider, providerPlaylists.keys() ) providerNames << provider->prettyName(); KDialog dialog; dialog.setCaption( i18n( "Confirm Playlist Deletion" ) ); dialog.setButtons( KDialog::Ok | KDialog::Cancel ); QLabel *label = new QLabel( i18np( "Are you sure you want to delete this playlist?", "Are you sure you want to delete these %1 playlists?", m_writableActionPlaylists.count() ), &dialog ); // TODO: include a text area with all the names of the playlists dialog.setButtonText( KDialog::Ok, i18nc( "%1 is playlist provider pretty name", "Yes, delete from %1.", providerNames.join( ", " ) ) ); dialog.setMainWidget( label ); if( dialog.exec() == QDialog::Accepted ) { foreach( PlaylistProvider *provider, providerPlaylists.keys() ) provider->deletePlaylists( providerPlaylists.value( provider ) ); } } void PlaylistBrowserView::slotRemoveTracks() { foreach( Playlists::PlaylistPtr playlist, m_writableActionTracks.uniqueKeys() ) { QList trackIndices = m_writableActionTracks.values( playlist ); qSort( trackIndices ); int removed = 0; foreach( int trackIndex, trackIndices ) { playlist->removeTrack( trackIndex - removed /* account for already removed */ ); removed++; } } } void PlaylistBrowserView::slotExport() { if( m_actionPlaylists.count() != 1 ) { warning() << __PRETTY_FUNCTION__ << "m_actionPlaylists.count() is not 1"; return; } Playlists::PlaylistPtr playlist = m_actionPlaylists.at( 0 ); // --- display save location dialog // compare with MainWindow::exportPlaylist // TODO: have this code only once QCheckBox *saveRelativeCheck = new QCheckBox( i18n("Use relative path for &saving") ); saveRelativeCheck->setChecked( AmarokConfig::relativePlaylist() ); - KFileDialog fileDialog( KUrl( "kfiledialog:///amarok-playlist-export" ), QString(), 0, saveRelativeCheck ); + KFileDialog fileDialog( QUrl("kfiledialog:///amarok-playlist-export"), QString(), 0, saveRelativeCheck ); QStringList supportedMimeTypes; supportedMimeTypes << "video/x-ms-asf"; // ASX supportedMimeTypes << "audio/x-mpegurl"; // M3U supportedMimeTypes << "audio/x-scpls"; // PLS supportedMimeTypes << "application/xspf+xml"; // XSPF fileDialog.setSelection( playlist->name() ); fileDialog.setMimeFilter( supportedMimeTypes, supportedMimeTypes.value( 1 ) ); fileDialog.setOperationMode( KFileDialog::Saving ); fileDialog.setMode( KFile::File ); fileDialog.setCaption( i18n("Save As") ); fileDialog.setObjectName( "PlaylistExport" ); fileDialog.exec(); QString playlistPath = fileDialog.selectedFile(); // --- actually save the playlist if( !playlistPath.isEmpty() ) Playlists::exportPlaylistFile( playlist->tracks(), playlistPath, saveRelativeCheck->isChecked() ); } void PlaylistBrowserView::insertIntoPlaylist( const QModelIndex &index, Playlist::AddOptions options ) { insertIntoPlaylist( QModelIndexList() << index, options ); } void PlaylistBrowserView::insertIntoPlaylist( const QModelIndexList &list, Playlist::AddOptions options ) { actionsFor( list ); // sets action targets insertIntoPlaylist( options ); resetActionTargets(); } void PlaylistBrowserView::insertIntoPlaylist( Playlist::AddOptions options ) { Meta::TrackList tracks; // add tracks for fully-selected playlists: foreach( Playlists::PlaylistPtr playlist, m_actionPlaylists ) { tracks << playlist->tracks(); } // filter-out tracks from playlists that are selected, add lone tracks: foreach( Playlists::PlaylistPtr playlist, m_actionTracks.uniqueKeys() ) { if( m_actionPlaylists.contains( playlist ) ) continue; Meta::TrackList playlistTracks = playlist->tracks(); QList positions = m_actionTracks.values( playlist ); qSort( positions ); foreach( int position, positions ) { if( position >= 0 && position < playlistTracks.count() ) tracks << playlistTracks.at( position ); } } if( !tracks.isEmpty() ) The::playlistController()->insertOptioned( tracks, options ); } diff --git a/src/browsers/playlistbrowser/PodcastCategory.cpp b/src/browsers/playlistbrowser/PodcastCategory.cpp index 706b6c8316..f6c9ce1b3a 100644 --- a/src/browsers/playlistbrowser/PodcastCategory.cpp +++ b/src/browsers/playlistbrowser/PodcastCategory.cpp @@ -1,283 +1,283 @@ /**************************************************************************************** * Copyright (c) 2007-2010 Bart Cerneels * * Copyright (c) 2007-2008 Nikolaj Hald Nielsen * * Copyright (c) 2007 Henry de Valence * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "PodcastCategory" #include "PodcastCategory.h" #include "amarokconfig.h" #include "amarokurls/AmarokUrl.h" #include "App.h" #include "browsers/InfoProxy.h" #include "core/support/Debug.h" #include "core/meta/support/MetaUtility.h" #include "PaletteHandler.h" #include "PodcastModel.h" #include "PlaylistBrowserView.h" #include "widgets/PrettyTreeRoles.h" #include #include #include #include #include #include #include #include #include namespace The { PlaylistBrowserNS::PodcastCategory* podcastCategory() { return PlaylistBrowserNS::PodcastCategory::instance(); } } using namespace PlaylistBrowserNS; QString PodcastCategory::s_configGroup( "Podcast View" ); PodcastCategory* PodcastCategory::s_instance = 0; PodcastCategory* PodcastCategory::instance() { return s_instance ? s_instance : new PodcastCategory( 0 ); } void PodcastCategory::destroy() { if( s_instance ) { delete s_instance; s_instance = 0; } } PodcastCategory::PodcastCategory( QWidget *parent ) : PlaylistBrowserCategory( Playlists::PodcastChannelPlaylist, "podcasts", s_configGroup, The::podcastModel(), parent ) { setPrettyName( i18n( "Podcasts" ) ); setShortDescription( i18n( "List of podcast subscriptions and episodes" ) ); setIcon( KIcon( "podcast-amarok" ) ); setLongDescription( i18n( "Manage your podcast subscriptions and browse individual episodes. " "Downloading episodes to the disk is also done here, or you can tell " "Amarok to do this automatically." ) ); setImagePath( KStandardDirs::locate( "data", "amarok/images/hover_info_podcasts.png" ) ); // set background if( AmarokConfig::showBrowserBackgroundImage() ) setBackgroundImage( imagePath() ); QAction *addPodcastAction = new QAction( KIcon( "list-add-amarok" ), i18n("&Add Podcast"), m_toolBar ); addPodcastAction->setPriority( QAction::NormalPriority ); m_toolBar->insertAction( m_separator, addPodcastAction ); connect( addPodcastAction, SIGNAL(triggered(bool)), The::podcastModel(), SLOT(addPodcast()) ); QAction *updateAllAction = new QAction( KIcon("view-refresh-amarok"), QString(), m_toolBar ); updateAllAction->setToolTip( i18n("&Update All") ); updateAllAction->setPriority( QAction::LowPriority ); m_toolBar->insertAction( m_separator, updateAllAction ); connect( updateAllAction, SIGNAL(triggered(bool)), The::podcastModel(), SLOT(refreshPodcasts()) ); QAction *importOpmlAction = new QAction( KIcon("document-import") , i18n( "Import OPML File" ) , m_toolBar ); importOpmlAction->setToolTip( i18n( "Import OPML File" ) ); importOpmlAction->setPriority( QAction::LowPriority ); m_toolBar->addAction( importOpmlAction ); connect( importOpmlAction, SIGNAL(triggered()), SLOT(slotImportOpml()) ); PlaylistBrowserView *view = static_cast( playlistView() ); connect( view, SIGNAL(currentItemChanged(QModelIndex)), SLOT(showInfo(QModelIndex)) ); //transparency // QPalette p = m_podcastTreeView->palette(); // QColor c = p.color( QPalette::Base ); // c.setAlpha( 0 ); // p.setColor( QPalette::Base, c ); // // c = p.color( QPalette::AlternateBase ); // c.setAlpha( 77 ); // p.setColor( QPalette::AlternateBase, c ); // // m_podcastTreeView->setPalette( p ); // // QSizePolicy sizePolicy1(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding); // sizePolicy1.setHorizontalStretch(0); // sizePolicy1.setVerticalStretch(0); // sizePolicy1.setHeightForWidth(m_podcastTreeView->sizePolicy().hasHeightForWidth()); // m_podcastTreeView->setSizePolicy(sizePolicy1); } PodcastCategory::~PodcastCategory() { } void PodcastCategory::showInfo( const QModelIndex &index ) { if( !index.isValid() ) return; const int row = index.row(); QString description; QString title( index.data( Qt::DisplayRole ).toString() ); QString subtitle( index.sibling( row, SubtitleColumn ).data( Qt::DisplayRole ).toString() ); - KUrl imageUrl( qvariant_cast( + QUrl imageUrl( qvariant_cast( index.sibling( row, ImageColumn ).data( Qt::DisplayRole ) ) ); QString author( index.sibling( row, AuthorColumn ).data( Qt::DisplayRole ).toString() ); QStringList keywords( qvariant_cast( index.sibling( row, KeywordsColumn ).data( Qt::DisplayRole ) ) ); bool isEpisode = index.sibling( row, IsEpisodeColumn ).data( Qt::DisplayRole ).toBool(); QString authorAndPubDate; if( !author.isEmpty() ) { authorAndPubDate = QString( "%1 %2 " ) .arg( i18n( "By" ) ) .arg( Qt::escape( author ) ); } if( !subtitle.isEmpty() ) { description += QString( "

%1

" ) .arg( Qt::escape( subtitle ) ); } if( !imageUrl.isEmpty() ) { description += QString( "

" ) .arg( Qt::escape( imageUrl.url() ) ); } if( isEpisode ) { QDateTime pubDate( index.sibling( row, DateColumn ).data( Qt::DisplayRole ).toDateTime() ); if( pubDate.isValid() ) { authorAndPubDate += QString( "%1 %2" ) .arg( i18nc( "Podcast published on date", "On" ) ) .arg( KGlobal::locale()->formatDateTime( pubDate, KLocale::FancyShortDate ) ); } } if( !authorAndPubDate.isEmpty() ) { description += QString( "

%1

" ) .arg( authorAndPubDate ); } if( isEpisode ) { int fileSize = index.sibling( row, FilesizeColumn ).data( Qt::DisplayRole ).toInt(); if( fileSize != 0 ) { description += QString( "

%1 %2

" ) .arg( i18n( "File Size:" ) ) .arg( Meta::prettyFilesize( fileSize ) ); } } else { QDate subsDate( index.sibling( row, DateColumn ).data( Qt::DisplayRole ).toDate() ); if( subsDate.isValid() ) { description += QString( "

%1 %2

" ) .arg( i18n( "Subscription Date:" ) ) .arg( KGlobal::locale()->formatDate( subsDate, KLocale::FancyShortDate ) ); } } if( !keywords.isEmpty() ) { description += QString( "

%1 %2

" ) .arg( i18n( "Keywords:" ) ) .arg( Qt::escape( keywords.join( ", " ) ) ); } description += index.data( PrettyTreeRoles::ByLineRole ).toString(); description = QString( "" " " " %1" " " " " " " "

%1

" " %2" " " "") .arg( Qt::escape( title ) ) .arg( description ) .arg( App::instance()->palette().brush( QPalette::Text ).color().name() ) .arg( PaletteHandler::highlightColor().name() ); QVariantMap map; map["service_name"] = title; map["main_info"] = description; The::infoProxy()->setInfo( map ); } void PodcastCategory::slotImportOpml() { AmarokUrl( "amarok://service-podcastdirectory/addOpml" ).run(); } diff --git a/src/browsers/playlistbrowser/PodcastModel.cpp b/src/browsers/playlistbrowser/PodcastModel.cpp index 6f9b935419..5bc0cb423f 100644 --- a/src/browsers/playlistbrowser/PodcastModel.cpp +++ b/src/browsers/playlistbrowser/PodcastModel.cpp @@ -1,376 +1,376 @@ /**************************************************************************************** * Copyright (c) 2007-2010 Bart Cerneels * * * * 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, see . * ****************************************************************************************/ #include "PodcastModel.h" #include "AmarokMimeData.h" #include "context/popupdropper/libpud/PopupDropper.h" #include "context/popupdropper/libpud/PopupDropperItem.h" #include "core/podcasts/PodcastImageFetcher.h" #include "core/podcasts/PodcastMeta.h" #include "core/support/Debug.h" #include "playlistmanager/PlaylistManager.h" #include "playlistmanager/SyncedPodcast.h" #include "PodcastCategory.h" #include "SvgHandler.h" #include #include "widgets/PrettyTreeRoles.h" #include #include #include #include #include using namespace Podcasts; namespace The { PlaylistBrowserNS::PodcastModel* podcastModel() { return PlaylistBrowserNS::PodcastModel::instance(); } } PlaylistBrowserNS::PodcastModel* PlaylistBrowserNS::PodcastModel::s_instance = 0; PlaylistBrowserNS::PodcastModel* PlaylistBrowserNS::PodcastModel::instance() { return s_instance ? s_instance : new PodcastModel(); } void PlaylistBrowserNS::PodcastModel::destroy() { if ( s_instance ) { delete s_instance; s_instance = 0; } } PlaylistBrowserNS::PodcastModel::PodcastModel() : PlaylistBrowserModel( PlaylistManager::PodcastChannel ) { s_instance = this; } bool PlaylistBrowserNS::PodcastModel::isOnDisk( PodcastEpisodePtr episode ) const { bool isOnDisk = false; - KUrl episodeFile( episode->localUrl() ); + QUrl episodeFile( episode->localUrl() ); if( !episodeFile.isEmpty() ) { isOnDisk = QFileInfo( episodeFile.toLocalFile() ).exists(); // reset localUrl because the file is not there. // FIXME: changing a podcast in innoncent-looking getter method is convoluted if( !isOnDisk ) - episode->setLocalUrl( KUrl() ); + episode->setLocalUrl( QUrl() ); } return isOnDisk; } QVariant PlaylistBrowserNS::PodcastModel::icon( const PodcastChannelPtr &channel ) const { QStringList emblems; //TODO: only check visible episodes. For now those are all returned by episodes(). foreach( const Podcasts::PodcastEpisodePtr ep, channel->episodes() ) { if( ep->isNew() ) { emblems << "rating"; break; } } if( channel->hasImage() ) { QSize size( channel->image().size() ); QPixmap pixmap( 32, 32 ); pixmap.fill( Qt::transparent ); size.scale( 32, 32, Qt::KeepAspectRatio ); int x = 32 / 2 - size.width() / 2; int y = 32 / 2 - size.height() / 2; QPainter p( &pixmap ); p.drawPixmap( x, y, QPixmap::fromImage( channel->image().scaled( size, Qt::KeepAspectRatio, Qt::SmoothTransformation ) ) ); // if it's a new episode draw the overlay: if( !emblems.isEmpty() ) // draw the overlay the same way KIconLoader does: p.drawPixmap( 2, 32 - 16 - 2, KIcon( "rating" ).pixmap( 16, 16 ) ); p.end(); return pixmap; } else return KIcon( "podcast-amarok", 0, emblems ).pixmap( 32, 32 ); } QVariant PlaylistBrowserNS::PodcastModel::icon( const PodcastEpisodePtr &episode ) const { QStringList emblems; if( isOnDisk( episode ) ) emblems << "go-down"; if( episode->isNew() ) return KIcon( "rating", 0, emblems ).pixmap( 24, 24 ); else return KIcon( "podcast-amarok", 0, emblems ).pixmap( 24, 24 ); } QVariant PlaylistBrowserNS::PodcastModel::data( const QModelIndex &idx, int role ) const { if( !idx.isValid() ) return PlaylistBrowserModel::data( idx, role ); if( IS_TRACK(idx) ) return episodeData( episodeForIndex( idx ), idx, role ); else return channelData( channelForIndex( idx ), idx, role ); } QVariant PlaylistBrowserNS::PodcastModel::channelData( const PodcastChannelPtr &channel, const QModelIndex &idx, int role ) const { if( !channel ) return QVariant(); switch( role ) { case Qt::DisplayRole: case Qt::ToolTipRole: switch( idx.column() ) { case PlaylistBrowserModel::PlaylistItemColumn: return channel->title(); case SubtitleColumn: return channel->subtitle(); case AuthorColumn: return channel->author(); case KeywordsColumn: return channel->keywords(); case ImageColumn: { - KUrl imageUrl( PodcastImageFetcher::cachedImagePath( channel ) ); + QUrl imageUrl( PodcastImageFetcher::cachedImagePath( channel ) ); if( !QFile( imageUrl.toLocalFile() ).exists() ) imageUrl = channel->imageUrl(); return imageUrl; } case DateColumn: channel->subscribeDate(); case IsEpisodeColumn: return false; } break; case PrettyTreeRoles::ByLineRole: if( idx.column() == PlaylistBrowserModel::ProviderColumn ) { Playlists::PlaylistProvider *provider = providerForIndex( idx ); if( provider ) return i18ncp( "number of podcasts from one source", "One Channel", "%1 channels", provider->playlists().count() ); } if( idx.column() == PlaylistBrowserModel::PlaylistItemColumn ) return channel->description(); break; case PrettyTreeRoles::HasCoverRole: return idx.column() == PlaylistBrowserModel::PlaylistItemColumn; case Qt::DecorationRole: if( idx.column() == PlaylistBrowserModel::PlaylistItemColumn ) return icon( channel ); break; } return PlaylistBrowserModel::data( idx, role ); } QVariant PlaylistBrowserNS::PodcastModel::episodeData( const PodcastEpisodePtr &episode, const QModelIndex &idx, int role ) const { if( !episode ) return QVariant(); switch( role ) { case Qt::DisplayRole: case Qt::ToolTipRole: switch( idx.column() ) { case PlaylistBrowserModel::PlaylistItemColumn: return episode->title(); case SubtitleColumn: return episode->subtitle(); case AuthorColumn: return episode->author(); case KeywordsColumn: return episode->keywords(); case FilesizeColumn: return episode->filesize(); case DateColumn: return episode->pubDate(); case IsEpisodeColumn: return true; } break; case PrettyTreeRoles::ByLineRole: if( idx.column() == PlaylistBrowserModel::ProviderColumn ) { Playlists::PlaylistProvider *provider = providerForIndex( idx ); if( provider ) return i18ncp( "number of podcasts from one source", "One Channel", "%1 channels", provider->playlists().count() ); } if( idx.column() == PlaylistBrowserModel::PlaylistItemColumn ) return episode->description(); break; case PrettyTreeRoles::HasCoverRole: return ( idx.column() == PlaylistBrowserModel::PlaylistItemColumn ); case Qt::DecorationRole: if( idx.column() == PlaylistBrowserModel::PlaylistItemColumn ) return icon( episode ); break; case EpisodeIsNewRole: return episode->isNew(); } return PlaylistBrowserModel::data( idx, role ); } bool PlaylistBrowserNS::PodcastModel::setData( const QModelIndex &idx, const QVariant &value, int role ) { PodcastEpisodePtr episode = episodeForIndex( idx ); if( !episode || !value.canConvert() || role != EpisodeIsNewRole ) { return PlaylistBrowserModel::setData( idx, value, role ); } bool checked = value.toBool(); episode->setNew( checked ); if( checked ) emit episodeMarkedAsNew( episode ); emit dataChanged( idx, idx ); return true; } int PlaylistBrowserNS::PodcastModel::columnCount( const QModelIndex &parent ) const { Q_UNUSED( parent ) return ColumnCount; } QVariant PlaylistBrowserNS::PodcastModel::headerData( int section, Qt::Orientation orientation, int role) const { if( orientation == Qt::Horizontal && role == Qt::DisplayRole ) { switch( section ) { case 0: return i18n("Type"); case 1: return i18n("Title"); case 2: return i18n("Summary"); default: return QVariant(); } } return QVariant(); } void PlaylistBrowserNS::PodcastModel::addPodcast() { debug() << "adding Podcast"; //TODO: request the user to which PodcastProvider he wants to add it in case // of multiple (enabled) Podcast Providers. Podcasts::PodcastProvider *podcastProvider = The::playlistManager()->defaultPodcasts(); if( podcastProvider ) { bool ok; QString url = QInputDialog::getText( 0, i18n("Add Podcast"), i18n("Enter RSS 1.0/2.0 or Atom feed URL:"), QLineEdit::Normal, QString(), &ok ); if( ok && !url.isEmpty() ) { // user entered something and pressed OK podcastProvider->addPodcast( Podcasts::PodcastProvider::toFeedUrl( url.trimmed() ) ); } else { // user entered nothing or pressed Cancel debug() << "invalid input or cancel"; } } else { debug() << "PodcastChannel provider is null"; } } void PlaylistBrowserNS::PodcastModel::refreshPodcasts() { foreach( Playlists::PlaylistProvider *provider, The::playlistManager()->providersForCategory( PlaylistManager::PodcastChannel ) ) { PodcastProvider *podcastProvider = dynamic_cast( provider ); if( podcastProvider ) podcastProvider->updateAll(); } } Podcasts::PodcastChannelPtr PlaylistBrowserNS::PodcastModel::channelForIndex( const QModelIndex &idx ) const { return Podcasts::PodcastChannelPtr::dynamicCast( playlistFromIndex( idx ) ); } Podcasts::PodcastEpisodePtr PlaylistBrowserNS::PodcastModel::episodeForIndex( const QModelIndex &idx ) const { return Podcasts::PodcastEpisodePtr::dynamicCast( trackFromIndex( idx ) ); } Meta::TrackList PlaylistBrowserNS::PodcastModel::podcastEpisodesToTracks( Podcasts::PodcastEpisodeList episodes ) { Meta::TrackList tracks; foreach( Podcasts::PodcastEpisodePtr episode, episodes ) tracks << Meta::TrackPtr::staticCast( episode ); return tracks; } diff --git a/src/configdialog/dialogs/ScriptsConfig.cpp b/src/configdialog/dialogs/ScriptsConfig.cpp index 364e471184..a289aba84c 100644 --- a/src/configdialog/dialogs/ScriptsConfig.cpp +++ b/src/configdialog/dialogs/ScriptsConfig.cpp @@ -1,300 +1,300 @@ /**************************************************************************************** * Copyright (c) 2010 Rick W. Chen * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "ScriptsConfig" #include "ScriptsConfig.h" #include "amarokconfig.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include "scripting/scriptmanager/ScriptManager.h" #include "ScriptSelector.h" #include "ui_ScriptsConfig.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include ScriptsConfig::ScriptsConfig( QWidget *parent ) : ConfigDialogBase( parent ) , m_configChanged( false ) , m_parent( parent ) , m_oldSelector( 0 ) { DEBUG_BLOCK Ui::ScriptsConfig gui; gui.setupUi( this ); m_uninstallButton = gui.uninstallButton; m_timer = new QTimer(this); connect( m_timer, SIGNAL(timeout()), this, SLOT(slotUpdateScripts()) ); m_timer->setInterval( 200 ); // Load config gui.kcfg_AutoUpdateScripts->setChecked( AmarokConfig::autoUpdateScripts() ); gui.manageButton->setIcon( KIcon( "get-hot-new-stuff-amarok" ) ); connect( gui.manageButton, SIGNAL(clicked()), SLOT(slotManageScripts()) ); connect( gui.installButton, SIGNAL(clicked(bool)), SLOT(installLocalScript()) ); m_selector = gui.scriptSelector; m_verticalLayout = gui.verticalLayout; slotReloadScriptSelector(); connect( gui.reloadButton, SIGNAL(clicked(bool)), m_timer, SLOT(start()) ); connect( gui.uninstallButton, SIGNAL(clicked(bool)), this, SLOT(slotUninstallScript()) ); connect( ScriptManager::instance(), SIGNAL(scriptsChanged()), SLOT(slotReloadScriptSelector()) ); this->setEnabled( AmarokConfig::enableScripts() ); } ScriptsConfig::~ScriptsConfig() {} void ScriptsConfig::slotManageScripts() { QStringList updateScriptsList; KNS3::DownloadDialog dialog("amarok.knsrc", this); dialog.exec(); if( !dialog.installedEntries().isEmpty() || !dialog.changedEntries().isEmpty() ) m_timer->start(); } void ScriptsConfig::updateSettings() { DEBUG_BLOCK if( m_configChanged ) { m_selector->save(); ScriptManager::instance()->configChanged( true ); } } bool ScriptsConfig::hasChanged() { return m_configChanged; } bool ScriptsConfig::isDefault() { return false; } void ScriptsConfig::slotConfigChanged( bool changed ) { m_configChanged = changed; if( changed ) debug() << "config changed"; } void ScriptsConfig::installLocalScript() { DEBUG_BLOCK // where's this config stored anyway, use amarokconfig instead? // the script can actually be updated if you get the folder name right int response = KMessageBox::warningContinueCancel( this, i18n( "Manually installed scripts " "cannot be automatically updated, continue?" ), QString(), KStandardGuiItem::cont() , KStandardGuiItem::cancel(), "manualScriptInstallWarning" ); if( response == KMessageBox::Cancel ) return; - QString filePath = KFileDialog::getOpenFileName( KUrl(), QString(), this, i18n( "Select Archived Script" ) ); + QString filePath = KFileDialog::getOpenFileName( QUrl(), QString(), this, i18n( "Select Archived Script" ) ); if( filePath.isEmpty() ) return; QString fileName = QFileInfo( filePath ).fileName(); QMimeType mimeType = db.mimeTypeForFile( filePath ); QScopedPointer archive; if( mimeType.inherits( "application/zip" ) ) archive.reset( new KZip( filePath ) ); else archive.reset( new KTar( filePath ) ); if( !archive || !archive->open( QIODevice::ReadOnly ) ) { KMessageBox::error( this, i18n( "Invalid Archive" ) ); return; } QString destination = KGlobal::dirs()->saveLocation( "data", QString("amarok/scripts/") + fileName + "/" , false ); const KArchiveDirectory* const archiveDir = archive->directory(); const QDir dir( destination ); const KArchiveFile *specFile = findSpecFile( archiveDir ); if( !specFile ) { KMessageBox::error( this, i18n( "Invalid Script File" ) ); return; } QTemporaryFile tempFile; tempFile.open(); QIODevice *device = specFile->createDevice(); tempFile.write( device->readAll() ); delete device; tempFile.close(); KPluginInfo newScriptInfo( tempFile.fileName() ); if( !newScriptInfo.isValid() ) { KMessageBox::error( this, i18n( "Invalid Script File" ) ); return; } if( ScriptManager::instance()->m_scripts.contains( newScriptInfo.pluginName() ) ) { QString existingVersion = ScriptManager::instance()->m_scripts[ newScriptInfo.pluginName() ]->info().version(); QString message = i18n( "Another script with the name %1 already exists\nExisting Script's " "Version: %1\nSelected Script's Version: %2", newScriptInfo.version() , existingVersion, newScriptInfo.version() ); KMessageBox::error( this, message ); return; } for( int i = 1; dir.exists( destination ); ++i ) destination += i; dir.mkpath( destination ); archiveDir->copyTo( destination ); KMessageBox::information( this, i18n( "The script %1 was successfully installed", newScriptInfo.name() ) ); m_timer->start(); } void ScriptsConfig::slotReloadScriptSelector() { DEBUG_BLOCK m_oldSelector = m_selector; m_selector = new ScriptSelector( this ); QString key = QLatin1String( "Generic" ); m_selector->addScripts( ScriptManager::instance()->scripts( key ), KPluginSelector::ReadConfigFile, i18n("Generic"), key ); key = QLatin1String( "Lyrics" ); m_selector->addScripts( ScriptManager::instance()->scripts( key ), KPluginSelector::ReadConfigFile, i18n("Lyrics"), key ); key = QLatin1String( "Scriptable Service" ); m_selector->addScripts( ScriptManager::instance()->scripts( key ), KPluginSelector::ReadConfigFile, i18n("Scriptable Service"), key ); connect( m_selector, SIGNAL(changed(bool)), SLOT(slotConfigChanged(bool)) ); connect( m_selector, SIGNAL(changed(bool)), m_parent, SLOT(updateButtons()) ); connect( m_selector, SIGNAL(filtered(bool)), m_uninstallButton, SLOT(setDisabled(bool)) ); m_verticalLayout->insertWidget( 0, m_selector ); m_verticalLayout->removeWidget( m_oldSelector ); m_selector->setFilter( m_oldSelector->filter() ); QTimer::singleShot( 0, this, SLOT(restoreScrollBar()) ); } void ScriptsConfig::restoreScrollBar() { if( !m_oldSelector ) return; m_selector->setVerticalPosition( m_oldSelector->verticalPosition() ); m_oldSelector->deleteLater(); } void ScriptsConfig::slotUpdateScripts() { m_timer->stop(); ScriptManager::instance()->updateAllScripts(); } void ScriptsConfig::slotUninstallScript() { DEBUG_BLOCK if( !ScriptManager::instance()->m_scripts.contains( m_selector->currentItem() ) ) return; ScriptItem *item = ScriptManager::instance()->m_scripts.value( m_selector->currentItem() ); int response = KMessageBox::warningContinueCancel( this, i18n( "You are advised to only uninstall manually " "installed scripts using this button." ) ); if( response == KMessageBox::Cancel ) return; QRegExp regex( "(.*apps/amarok/scripts/.+/).*script.spec" ); regex.indexIn( item->info().entryPath() ); qDebug() << "About to remove folder " << regex.cap( 1 ); removeDir( regex.cap( 1 ) ); m_timer->start(); } const KArchiveFile* ScriptsConfig::findSpecFile( const KArchiveDirectory *dir ) const { foreach( const QString &entry, dir->entries() ) { if( dir->entry( entry )->isFile() ) { if( entry == "script.spec" ) return static_cast( dir->entry( entry ) ); } else { if( entry != "." && entry != ".." ) { const KArchiveDirectory *subDir = static_cast( dir->entry( entry ) ); if( subDir ) { const KArchiveFile *file = findSpecFile( subDir ); if( !file ) continue; return file; } } } } return 0; } void ScriptsConfig::removeDir( const QString &dirPath ) const { QDir dir( dirPath ); if( dir.exists( dirPath ) ) { foreach( const QFileInfo &info, dir.entryInfoList( QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | QDir::AllDirs | QDir::Files ) ) { if( info.isDir() ) removeDir( info.absoluteFilePath() ); else QFile::remove( info.absoluteFilePath() ); } dir.rmdir( dirPath ); } } diff --git a/src/context/LyricsManager.cpp b/src/context/LyricsManager.cpp index d59207e0cb..6a07108faa 100644 --- a/src/context/LyricsManager.cpp +++ b/src/context/LyricsManager.cpp @@ -1,272 +1,272 @@ /**************************************************************************************** * Copyright (c) 2007 Leo Franchi * * Copyright (c) 2009 Seb Ruiz * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "LyricsManager" #include "LyricsManager.h" #include "EngineController.h" #include "core/meta/Meta.h" #include "core/support/Debug.h" #include "core-impl/collections/support/CollectionManager.h" #include #include #include #include //////////////////////////////////////////////////////////////// //// CLASS LyricsObserver /////////////////////////////////////////////////////////////// LyricsObserver::LyricsObserver() : m_subject( 0 ) { qRegisterMetaType("LyricsData"); } LyricsObserver::LyricsObserver( LyricsSubject *s ) : m_subject( s ) { m_subject->attach( this ); } LyricsObserver::~LyricsObserver() { if( m_subject ) m_subject->detach( this ); } //////////////////////////////////////////////////////////////// //// CLASS LyricsSubject /////////////////////////////////////////////////////////////// void LyricsSubject::sendNewLyrics( const LyricsData &lyrics ) { DEBUG_BLOCK foreach( LyricsObserver* obs, m_observers ) { obs->newLyrics( lyrics ); } } void LyricsSubject::sendNewSuggestions( const QVariantList &sug ) { DEBUG_BLOCK foreach( LyricsObserver* obs, m_observers ) { obs->newSuggestions( sug ); } } void LyricsSubject::sendLyricsMessage( const QString &key, const QString &val ) { DEBUG_BLOCK foreach( LyricsObserver* obs, m_observers ) { obs->lyricsMessage( key, val ); } } void LyricsSubject::attach( LyricsObserver *obs ) { if( !obs || m_observers.indexOf( obs ) != -1 ) return; m_observers.append( obs ); } void LyricsSubject::detach( LyricsObserver *obs ) { int index = m_observers.indexOf( obs ); if( index != -1 ) m_observers.removeAt( index ); } //////////////////////////////////////////////////////////////// //// CLASS LyricsManager /////////////////////////////////////////////////////////////// LyricsManager* LyricsManager::s_self = 0; void LyricsManager::lyricsResult( const QString& lyricsXML, bool cached ) //SLOT { DEBUG_BLOCK Q_UNUSED( cached ); QXmlStreamReader xml( lyricsXML ); while( xml.readNextStartElement() ) { if( xml.name() == QLatin1String("lyric") ) { Meta::TrackPtr currentTrack = The::engineController()->currentTrack(); if( !currentTrack ) return; QString lyrics( xml.readElementText() ); if( !isEmpty( lyrics ) ) { // overwrite cached lyrics (as either there were no lyircs available previously OR // the user exlicitly agreed to overwrite the lyrics) debug() << "setting cached lyrics..."; currentTrack->setCachedLyrics( lyrics ); // TODO: setLyricsByPath? } else if( !isEmpty( currentTrack->cachedLyrics() ) ) { // we found nothing, so if we have cached lyrics, use it! debug() << "using cached lyrics..."; lyrics = currentTrack->cachedLyrics(); } else { lyricsError( i18n("Retrieved lyrics is empty") ); return; } QString artist = currentTrack->artist() ? currentTrack->artist()->name() : QString(); - LyricsData data = { lyrics, currentTrack->name(), artist, KUrl() }; + LyricsData data = { lyrics, currentTrack->name(), artist, QUrl() }; sendNewLyrics( data ); return; } else if( xml.name() == QLatin1String("suggestions") ) { QVariantList suggestions; while( xml.readNextStartElement() ) { if( xml.name() != QLatin1String("suggestion") ) continue; const QXmlStreamAttributes &a = xml.attributes(); QString artist = a.value( QLatin1String("artist") ).toString(); QString title = a.value( QLatin1String("title") ).toString(); QString url = a.value( QLatin1String("url") ).toString(); if( !url.isEmpty() ) suggestions << ( QStringList() << title << artist << url ); xml.skipCurrentElement(); } debug() << "got" << suggestions.size() << "suggestions"; if( suggestions.isEmpty() ) sendLyricsMessage( "notFound", "notfound" ); else sendNewSuggestions( suggestions ); return; } xml.skipCurrentElement(); } if( xml.hasError() ) { warning() << "errors occurred during reading lyrics xml result:" << xml.errorString(); lyricsError( i18n("Lyrics data could not be parsed") ); } } void LyricsManager::lyricsResultHtml( const QString& lyricsHTML, bool cached ) { DEBUG_BLOCK Q_UNUSED( cached ) // we don't need to deal with suggestions here, because // we assume the script has called showLyrics if they could // be suggestions. this is for HTML display only Meta::TrackPtr currentTrack = The::engineController()->currentTrack(); if( currentTrack && !isEmpty( lyricsHTML ) ) { QString artist = currentTrack->artist() ? currentTrack->artist()->name() : QString(); - LyricsData data = { lyricsHTML, currentTrack->name(), artist, KUrl() }; + LyricsData data = { lyricsHTML, currentTrack->name(), artist, QUrl() }; sendNewLyrics( data ); // overwrite cached lyrics (as either there were no lyircs available previously OR // the user exlicitly agreed to overwrite the lyrics) currentTrack->setCachedLyrics( lyricsHTML ); } } void LyricsManager::lyricsError( const QString &error ) { DEBUG_BLOCK if( !showCached() ) { sendLyricsMessage( "error", error ); } } void LyricsManager::lyricsNotFound( const QString& notfound ) { DEBUG_BLOCK if( !showCached() ) sendLyricsMessage( "notfound", notfound ); } bool LyricsManager::showCached() { DEBUG_BLOCK //if we have cached lyrics there is absolutely no point in not showing these.. Meta::TrackPtr currentTrack = The::engineController()->currentTrack(); if( currentTrack && !isEmpty( currentTrack->cachedLyrics() ) ) { // TODO: add some sort of feedback that we could not fetch new ones // so we are showing a cached result debug() << "showing cached lyrics!"; QString lyrics = currentTrack->cachedLyrics(); QString artist = currentTrack->artist() ? currentTrack->artist()->name() : QString(); - LyricsData data = { lyrics, currentTrack->name(), artist, KUrl() }; + LyricsData data = { lyrics, currentTrack->name(), artist, QUrl() }; sendNewLyrics( data ); return true; } return false; } void LyricsManager::setLyricsForTrack( const QString &trackUrl, const QString &lyrics ) const { DEBUG_BLOCK - Meta::TrackPtr track = CollectionManager::instance()->trackForUrl( KUrl( trackUrl ) ); + Meta::TrackPtr track = CollectionManager::instance()->trackForUrl( QUrl( trackUrl ) ); if( track ) track->setCachedLyrics( lyrics ); else debug() << QString("could not find a track for the given URL (%1) - ignoring.").arg( trackUrl ); } bool LyricsManager::isEmpty( const QString &lyrics ) const { QGraphicsTextItem testItem; // Set the text of the TextItem. if( Qt::mightBeRichText( lyrics ) ) testItem.setHtml( lyrics ); else testItem.setPlainText( lyrics ); // Get the plaintext content. // We use toPlainText() to strip all Html formatting, // so we can test if there's any text given. QString testText = testItem.toPlainText().trimmed(); return testText.isEmpty(); } diff --git a/src/context/LyricsManager.h b/src/context/LyricsManager.h index 940fb78097..f3898697c7 100644 --- a/src/context/LyricsManager.h +++ b/src/context/LyricsManager.h @@ -1,132 +1,132 @@ /**************************************************************************************** * Copyright (c) 2007 Leo Franchi * * * * 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, see . * ****************************************************************************************/ #ifndef LYRICS_MANAGER_H #define LYRICS_MANAGER_H #include "amarok_export.h" -#include +#include #include #include #include #include class LyricsSubject; struct LyricsData { QString text; QString title; QString artist; - KUrl site; + QUrl site; void clear() { text.clear(); title.clear(); artist.clear(); site.clear(); } }; class AMAROK_EXPORT LyricsObserver { public: LyricsObserver(); LyricsObserver( LyricsSubject* ); virtual ~LyricsObserver(); /** * A lyrics script has returned new lyrics. */ virtual void newLyrics( const LyricsData &lyrics ) { Q_UNUSED( lyrics ); } /** * A lyrics script has returned a list of suggested URLs for correct lyrics. */ virtual void newSuggestions( const QVariantList &suggestions ) { Q_UNUSED( suggestions ); } /** * A lyrics script has returned some generic message that they want to be displayed. */ virtual void lyricsMessage( const QString& key, const QString &val ) { Q_UNUSED( key ); Q_UNUSED( val ); } private: LyricsSubject *m_subject; }; class LyricsSubject { public: void attach( LyricsObserver *observer ); void detach( LyricsObserver *observer ); protected: LyricsSubject() {} virtual ~LyricsSubject() {} void sendNewLyrics( const LyricsData &lyrics ); void sendNewSuggestions( const QVariantList &suggestions ); void sendLyricsMessage( const QString &key, const QString &val ); private: QList m_observers; }; class AMAROK_EXPORT LyricsManager : public LyricsSubject { public: static LyricsManager* self() { if( !s_self ) s_self = new LyricsManager(); return s_self; } void lyricsResult( const QString& lyrics, bool cached = false ); void lyricsResultHtml( const QString& lyrics, bool cached = false ); void lyricsError( const QString &error ); void lyricsNotFound( const QString& notfound ); /** * Sets the given lyrics for the track with the given URL. * * @param trackUrl The URL of the track. * @param lyrics The new lyrics. */ void setLyricsForTrack( const QString &trackUrl, const QString &lyrics ) const; /** * Tests if the given lyrics are empty. * * @param lyrics The lyrics which will be tested. * * @return true if the given lyrics are empty, otherwise false. */ bool isEmpty( const QString &lyrics ) const; private: LyricsManager() : LyricsSubject() { s_self = this; } bool showCached(); static LyricsManager* s_self; }; Q_DECLARE_METATYPE( LyricsData ); #endif diff --git a/src/context/applets/info/InfoApplet.cpp b/src/context/applets/info/InfoApplet.cpp index 420ba285eb..93b4d1b1d8 100644 --- a/src/context/applets/info/InfoApplet.cpp +++ b/src/context/applets/info/InfoApplet.cpp @@ -1,138 +1,138 @@ /**************************************************************************************** * Copyright (c) 2007 Leo Franchi * * Copyright (c) 2008 Nikolaj Hald Nielsen * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "InfoApplet" #include "InfoApplet.h" #include "App.h" #include "amarokurls/AmarokUrl.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include "PaletteHandler.h" #include "playlist/PlaylistController.h" #include #include #include #include #include QString InfoApplet::s_defaultHtml = "" " " " " " " " " " %%SUBJECT_NAME%%" " " ""; InfoApplet::InfoApplet( QObject* parent, const QVariantList& args ) : Context::Applet( parent, args ) , m_webView( 0 ) , m_initialized( false ) { setHasConfigurationInterface( false ); } InfoApplet::~InfoApplet() { delete m_webView; } void InfoApplet::init() { // Call the base implementation. Context::Applet::init(); dataEngine( "amarok-info" )->connectSource( "info", this ); m_webView = new KGraphicsWebView( this ); QPalette p = m_webView->palette(); p.setColor( QPalette::Dark, QColor( 255, 255, 255, 0) ); p.setColor( QPalette::Window, QColor( 255, 255, 255, 0) ); m_webView->setPalette( p ); QGraphicsLinearLayout *layout = new QGraphicsLinearLayout( Qt::Vertical, this ); layout->addItem( m_webView ); connect( m_webView->page(), SIGNAL(linkClicked(QUrl)), SLOT(linkClicked(QUrl)) ); updateConstraints(); } void InfoApplet::constraintsEvent( Plasma::Constraints constraints ) { Q_UNUSED( constraints ) m_initialized = true; } void InfoApplet::dataUpdated( const QString& name, const Plasma::DataEngine::Data& data ) { Q_UNUSED( name ); if( data.isEmpty() ) return; if( m_initialized ) { QString currentHtml = data[ "main_info" ].toString(); if ( !currentHtml.isEmpty() ) { QColor highlight( App::instance()->palette().highlight().color() ); highlight.setHsvF( highlight.hueF(), 0.3, .95, highlight.alphaF() ); currentHtml = currentHtml.replace( "{text_color}", App::instance()->palette().brush( QPalette::Text ).color().name() ); currentHtml = currentHtml.replace( "{content_background_color}", highlight.name() ); currentHtml = currentHtml.replace( "{background_color}", PaletteHandler::highlightColor().lighter( 150 ).name()); currentHtml = currentHtml.replace( "{border_color}", PaletteHandler::highlightColor().lighter( 150 ).name() ); - m_webView->setHtml( currentHtml, KUrl( QString() ) ); + m_webView->setHtml( currentHtml, QUrl( QString() ) ); } else { currentHtml = s_defaultHtml; currentHtml = currentHtml.replace( "%%SUBJECT_NAME%%", data[ "subject_name" ].toString() ); m_webView->setHtml( currentHtml ); } m_webView->page()->setLinkDelegationPolicy( QWebPage::DelegateAllLinks ); updateConstraints(); } } void InfoApplet::linkClicked( const QUrl & url ) { DEBUG_BLOCK debug() << "Link clicked: " << url.toString(); if ( url.toString().startsWith( "amarok://", Qt::CaseInsensitive ) ) { AmarokUrl aUrl( url.toString() ); aUrl.run(); } else if ( url.toString().contains( ".xspf", Qt::CaseInsensitive ) ) { // FIXME: this doesn't work (triggerTrackLoad is not called) and leaks the playlist instance new Playlists::XSPFPlaylist( url, 0, Playlists::XSPFPlaylist::AppendToPlaylist ); } else QDesktopServices::openUrl( url.toString() ); } diff --git a/src/context/applets/lyrics/LyricsApplet.cpp b/src/context/applets/lyrics/LyricsApplet.cpp index c59b94ce63..46d9bab0db 100644 --- a/src/context/applets/lyrics/LyricsApplet.cpp +++ b/src/context/applets/lyrics/LyricsApplet.cpp @@ -1,737 +1,737 @@ /**************************************************************************************** * Copyright (c) 2007 Leo Franchi * * Copyright (c) 2009 simon.esneault * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "LyricsApplet" #include "LyricsApplet.h" #include "EngineController.h" #include "context/widgets/AppletHeader.h" #include "core/meta/Meta.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include "context/LyricsManager.h" #include "LyricsBrowser.h" #include "LyricsSuggestionsListWidget.h" #include "scripting/scriptmanager/ScriptManager.h" #include #include #include #include #include #include #include #include class LyricsAppletPrivate { public: LyricsAppletPrivate( LyricsApplet *parent ) : saveIcon( 0 ) , editIcon( 0 ) , autoScrollIcon( 0 ) , reloadIcon( 0 ) , closeIcon( 0 ) , settingsIcon( 0 ) , browser( 0 ) , suggestView( 0 ) , currentTrack( 0 ) , alignment( Qt::AlignLeft ) , hasLyrics( false ) , showBrowser( false ) , showSuggestions( false ) , isShowingUnsavedWarning( false ) , userAutoScrollOffset( 0 ) , oldSliderPosition( 0 ) , q_ptr( parent ) {} ~LyricsAppletPrivate() {} // member functions void setEditing( bool isEditing ); void determineActionIconsState(); void refetchLyrics(); void showLyrics( const QString &text ); void showSuggested( const QVariantList &suggestions ); void showUnsavedChangesWarning( Meta::TrackPtr ); // private slots void _editLyrics(); void _changeLyricsAlignment(); void _changeLyricsFont(); void _closeLyrics(); void _saveLyrics(); void _toggleAutoScroll(); void _suggestionChosen( const LyricsSuggestion &suggestion ); void _unsetCursor(); void _trackDataChanged( Meta::TrackPtr ); void _trackPositionChanged( qint64 position, bool userSeek ); void _lyricsChangedMessageButtonPressed( const Plasma::MessageButton button ); void _refetchMessageButtonPressed( const Plasma::MessageButton button ); Plasma::IconWidget *saveIcon; Plasma::IconWidget *editIcon; Plasma::IconWidget *autoScrollIcon; Plasma::IconWidget *reloadIcon; Plasma::IconWidget *closeIcon; Plasma::IconWidget *settingsIcon; LyricsBrowser *browser; LyricsSuggestionsListWidget *suggestView; Ui::lyricsSettings ui_settings; Meta::TrackPtr currentTrack; Meta::TrackPtr modifiedTrack; QString modifiedLyrics; Qt::Alignment alignment; bool hasLyrics; bool showBrowser; bool autoScroll; bool showSuggestions; bool isShowingUnsavedWarning; int userAutoScrollOffset; int oldSliderPosition; private: LyricsApplet *const q_ptr; Q_DECLARE_PUBLIC( LyricsApplet ) }; void LyricsAppletPrivate::setEditing( bool isEditing ) { browser->setReadOnly( !isEditing ); } void LyricsAppletPrivate::determineActionIconsState() { bool isEditing = !browser->isReadOnly(); editIcon->action()->setEnabled( !isEditing ); closeIcon->action()->setEnabled( isEditing ); saveIcon->action()->setEnabled( isEditing ); autoScrollIcon->action()->setEnabled( !isEditing ); reloadIcon->action()->setEnabled( !isEditing ); } void LyricsAppletPrivate::showLyrics( const QString &text ) { browser->clear(); browser->setLyrics( text ); showSuggestions = false; showBrowser = true; determineActionIconsState(); } void LyricsAppletPrivate::showSuggested( const QVariantList &suggestions ) { editIcon->action()->setEnabled( false ); closeIcon->action()->setEnabled( false ); saveIcon->action()->setEnabled( false ); suggestView->clear(); foreach( const QVariant &suggestion, suggestions ) { QStringList s( suggestion.toStringList() ); QString title( s.at(0) ); QString artist( s.at(1) ); - KUrl url( s.at(2) ); + QUrl url( s.at(2) ); LyricsSuggestion lyricsSuggestion = { url, title, artist }; suggestView->add( lyricsSuggestion ); } showSuggestions = true; } void LyricsAppletPrivate::refetchLyrics() { DEBUG_BLOCK ScriptManager::instance()->notifyFetchLyrics( currentTrack->artist()->name(), currentTrack->name(), "", currentTrack ); } void LyricsAppletPrivate::showUnsavedChangesWarning( Meta::TrackPtr newTrack ) { Q_Q( LyricsApplet ); // Set the track which was modified and store the current // lyircs from the UI. modifiedTrack = currentTrack; modifiedLyrics = browser->lyrics(); QString artistName = modifiedTrack->artist() ? modifiedTrack->artist()->name() : i18nc( "Used if the current track has no artist.", "Unknown" ); QString warningMessage; // Check if the track has changed. if( newTrack != modifiedTrack ) { // Show a warning that the track has changed while the user was editing the lyrics for the current track. warningMessage = i18n( "While you were editing the lyrics of %1 - %2 the track has changed. Do you want to save your changes?", artistName, modifiedTrack->prettyName() ); } else { // Show a warning that the lyrics for the track were modified (for example by a script). warningMessage = i18n( "The lyrics of %1 - %2 changed while you were editing them. Do you want to save your changes?", artistName, modifiedTrack->prettyName() ); } // Show the warning message. q->showWarning( warningMessage, SLOT(_lyricsChangedMessageButtonPressed(Plasma::MessageButton)) ); // Make the contents readonly again. // Since the applet is now blocked the user can not enable this again. // Thus we can make sure that we won't overwrite modifiedTrack. setEditing( false ); isShowingUnsavedWarning = false; } void LyricsAppletPrivate::_refetchMessageButtonPressed( const Plasma::MessageButton button ) { DEBUG_BLOCK // Check if the user pressed "Yes". if( button == Plasma::ButtonYes ) // Refetch the lyrics. refetchLyrics(); } void LyricsAppletPrivate::_lyricsChangedMessageButtonPressed( const Plasma::MessageButton button ) { DEBUG_BLOCK // Check if the user pressed "Yes". if( button == Plasma::ButtonYes ) // Update the lyrics of the track. modifiedTrack->setCachedLyrics( modifiedLyrics ); modifiedLyrics.clear(); } void LyricsAppletPrivate::_changeLyricsAlignment() { if( ui_settings.alignLeft->isChecked() ) alignment = Qt::AlignLeft; else if( ui_settings.alignCenter->isChecked() ) alignment = Qt::AlignCenter; else if( ui_settings.alignRight->isChecked() ) alignment = Qt::AlignRight; Amarok::config("Lyrics Applet").writeEntry( "Alignment", int(alignment) ); browser->setAlignment( alignment ); } void LyricsAppletPrivate::_changeLyricsFont() { QFont font = ui_settings.fontChooser->font(); browser->nativeWidget()->setFont( font ); KConfigGroup config = Amarok::config("Lyrics Applet"); config.writeEntry( "Font", font.toString() ); debug() << "Setting Lyrics Applet font: " << font.family() << " " << font.pointSize(); } void LyricsAppletPrivate::_editLyrics() { if( !hasLyrics ) browser->clear(); Q_Q( LyricsApplet ); if( q->isCollapsed() ) q->setCollapseOff(); // disable autoscroll when starting editing if (autoScroll) _toggleAutoScroll(); if( !browser->isVisible() ) { browser->show(); suggestView->hide(); suggestView->clear(); QGraphicsLinearLayout *lo = static_cast( q->layout() ); lo->removeItem( suggestView ); lo->addItem( browser ); } browser->setAlignment( Qt::AlignLeft ); setEditing( true ); determineActionIconsState(); browser->nativeWidget()->ensureCursorVisible(); } void LyricsAppletPrivate::_closeLyrics() { if( hasLyrics ) { QScrollBar *vbar = browser->nativeWidget()->verticalScrollBar(); int savedPosition = vbar->isVisible() ? vbar->value() : vbar->minimum(); showLyrics( currentTrack->cachedLyrics() ); vbar->setSliderPosition( savedPosition ); // emit sizeHintChanged(Qt::MaximumSize); } else { browser->clear(); } setEditing( false ); browser->setAlignment( alignment ); determineActionIconsState(); } void LyricsAppletPrivate::_saveLyrics() { if( currentTrack ) { if( !LyricsManager::self()->isEmpty( browser->nativeWidget()->toPlainText() ) ) { currentTrack->setCachedLyrics( browser->lyrics() ); hasLyrics = true; } else { currentTrack->setCachedLyrics( QString() ); hasLyrics = false; } // emit sizeHintChanged(Qt::MaximumSize); } setEditing( false ); browser->setAlignment( alignment ); determineActionIconsState(); } void LyricsAppletPrivate::_toggleAutoScroll() { Q_Q( LyricsApplet ); Plasma::IconWidget *icon = qobject_cast(q->sender()); DEBUG_ASSERT( icon, return ) // that should not happen autoScroll = !autoScroll; icon->setPressed( autoScroll ); Amarok::config( "Lyrics Applet" ).writeEntry( "AutoScroll", autoScroll ); } void LyricsAppletPrivate::_suggestionChosen( const LyricsSuggestion &suggestion ) { DEBUG_BLOCK - KUrl url = suggestion.url; + QUrl url = suggestion.url; if( !url.isValid() ) return; QString title = suggestion.title; QString artist = suggestion.artist; Q_Q( LyricsApplet ); debug() << "clicked suggestion" << url; ScriptManager::instance()->notifyFetchLyrics( artist, title, url.url(), Meta::TrackPtr() ); suggestView->setCursor( Qt::BusyCursor ); QTimer::singleShot( 10000, q, SLOT(_unsetCursor()) ); } void LyricsAppletPrivate::_unsetCursor() { if( suggestView->hasCursor() ) suggestView->unsetCursor(); } void LyricsAppletPrivate::_trackDataChanged( Meta::TrackPtr track ) { userAutoScrollOffset = 0; oldSliderPosition = 0; // Check if we previously had a track. // If the lyrics currently shown in the browser (which // additionally is in edit mode) are different from the // lyrics of the track we have to show a warning. if( !isShowingUnsavedWarning && currentTrack && !browser->isReadOnly() && (currentTrack->cachedLyrics() != browser->lyrics()) ) { isShowingUnsavedWarning = true; showUnsavedChangesWarning( track ); } // Update the current track. currentTrack = track; } void LyricsAppletPrivate::_trackPositionChanged( qint64 position, bool userSeek ) { Q_UNUSED( userSeek ); EngineController *engine = The::engineController(); QScrollBar *vbar = browser->nativeWidget()->verticalScrollBar(); if( engine->trackPositionMs() != 0 && !vbar->isSliderDown() && autoScroll ) { userAutoScrollOffset = userAutoScrollOffset + vbar->value() - oldSliderPosition; //prevent possible devision by 0 (example streams). if( engine->trackLength() == 0 ) return; // Scroll to try and keep the current position in the lyrics centred. int newSliderPosition = position * (vbar->maximum() + vbar->pageStep()) / engine->trackLength() - vbar->pageStep() / 2 + userAutoScrollOffset; vbar->setSliderPosition( newSliderPosition ); oldSliderPosition = vbar->value(); } } LyricsApplet::LyricsApplet( QObject* parent, const QVariantList& args ) : Context::Applet( parent, args ) , d_ptr( new LyricsAppletPrivate( this ) ) { setHasConfigurationInterface( true ); setBackgroundHints( Plasma::Applet::NoBackground ); } LyricsApplet::~LyricsApplet() { delete d_ptr; } void LyricsApplet::init() { DEBUG_BLOCK Q_D( LyricsApplet ); // Call the base implementation. Context::Applet::init(); enableHeader( true ); setHeaderText( i18n( "Lyrics" ) ); setCollapseOffHeight( -1 ); setCollapseHeight( m_header->height() ); setMinimumHeight( collapseHeight() ); setPreferredHeight( collapseHeight() ); QAction* editAction = new QAction( this ); editAction->setIcon( KIcon( "document-edit" ) ); editAction->setEnabled( false ); editAction->setText( i18n( "Edit Lyrics" ) ); d->editIcon = addLeftHeaderAction( editAction ); connect( d->editIcon, SIGNAL(clicked()), this, SLOT(_editLyrics()) ); QAction* saveAction = new QAction( this ); saveAction->setIcon( KIcon( "document-save" ) ); saveAction->setEnabled( false ); saveAction->setText( i18n( "Save Lyrics" ) ); d->saveIcon = addLeftHeaderAction( saveAction ); connect( d->saveIcon, SIGNAL(clicked()), this, SLOT(_saveLyrics()) ); QAction* closeAction = new QAction( this ); closeAction->setIcon( KIcon( "document-close" ) ); closeAction->setEnabled( false ); closeAction->setText( i18n( "Close" ) ); d->closeIcon = addLeftHeaderAction( closeAction ); connect( d->closeIcon, SIGNAL(clicked()), this, SLOT(_closeLyrics()) ); QAction* autoScrollAction = new QAction( this ); autoScrollAction->setIcon( KIcon( QPixmap( KStandardDirs::locate( "data", "amarok/images/playlist-sorting-16.png" ) ) ) ); autoScrollAction->setEnabled( true ); autoScrollAction->setText( i18n( "Scroll automatically" ) ); d->autoScrollIcon = addRightHeaderAction( autoScrollAction ); connect( d->autoScrollIcon, SIGNAL(clicked()), this, SLOT(_toggleAutoScroll()) ); QAction* reloadAction = new QAction( this ); reloadAction->setIcon( KIcon( "view-refresh" ) ); reloadAction->setEnabled( true ); reloadAction->setText( i18n( "Reload Lyrics" ) ); d->reloadIcon = addRightHeaderAction( reloadAction ); connect( d->reloadIcon, SIGNAL(clicked()), this, SLOT(refreshLyrics()) ); QAction* settingsAction = new QAction( this ); settingsAction->setIcon( KIcon( "preferences-system" ) ); settingsAction->setEnabled( true ); settingsAction->setText( i18n( "Settings" ) ); d->settingsIcon = addRightHeaderAction( settingsAction ); connect( d->settingsIcon, SIGNAL(clicked()), this, SLOT(showConfigurationInterface()) ); d->browser = new LyricsBrowser( this ); d->browser->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ); d->browser->hide(); d->suggestView = new LyricsSuggestionsListWidget( this ); d->suggestView->hide(); QGraphicsLinearLayout *layout = new QGraphicsLinearLayout( Qt::Vertical ); layout->addItem( m_header ); layout->addItem( d->browser ); setLayout( layout ); // Read config const KConfigGroup &lyricsConfig = Amarok::config("Lyrics Applet"); d->alignment = Qt::Alignment( lyricsConfig.readEntry("Alignment", int(Qt::AlignLeft)) ); d->browser->setAlignment( d->alignment ); d->autoScroll = lyricsConfig.readEntry( "AutoScroll", true ); d->autoScrollIcon->setPressed( d->autoScroll ); QFont font; if( font.fromString( lyricsConfig.readEntry("Font", QString()) ) ) d->browser->setFont( font ); EngineController* engine = The::engineController(); connect( engine, SIGNAL(trackChanged(Meta::TrackPtr)), this, SLOT(_trackDataChanged(Meta::TrackPtr)) ); connect( engine, SIGNAL(trackMetadataChanged(Meta::TrackPtr)), this, SLOT(_trackDataChanged(Meta::TrackPtr)) ); connect( engine, SIGNAL(trackPositionChanged(qint64,bool)), this, SLOT(_trackPositionChanged(qint64,bool)) ); connect( d->suggestView, SIGNAL(selected(LyricsSuggestion)), SLOT(_suggestionChosen(LyricsSuggestion)) ); connect( dataEngine("amarok-lyrics"), SIGNAL(sourceAdded(QString)), this, SLOT(connectSource(QString)) ); // This is needed as a track might be playing when the lyrics applet // is added to the ContextView. d->_trackDataChanged( engine->currentTrack() ); d->_trackPositionChanged( engine->trackPositionMs(), false ); d->determineActionIconsState(); connectSource( "lyrics" ); } void LyricsApplet::connectSource( const QString& source ) { if( source == "lyrics" ) { dataEngine( "amarok-lyrics" )->connectSource( source, this ); refreshLyrics(); // get data initially } else if( source == "suggested" ) { dataEngine( "amarok-lyrics" )->connectSource( source, this ); dataUpdated( source, dataEngine("amarok-lyrics" )->query( "suggested" ) ); } } void LyricsApplet::dataUpdated( const QString& name, const Plasma::DataEngine::Data& data ) { Q_D( LyricsApplet ); if( name != QLatin1String("lyrics") ) return; unsetCursor(); d->hasLyrics = false; d->showSuggestions = false; d->showBrowser = false; setBusy( false ); QString titleText; if( data.contains( "noscriptrunning" ) ) { titleText = i18n( "Lyrics: No script is running" ); setCollapseOn(); } else if( data.contains( "stopped" ) ) { titleText = i18n( "Lyrics" ); setCollapseOn(); } else if( data.contains( "fetching" ) ) { if( canAnimate() ) setBusy( true ); titleText = i18n( "Lyrics: Fetching ..." ); } else if( data.contains( "error" ) ) { titleText = i18n( "Lyrics: Fetch error" ); setCollapseOn(); } else if( data.contains( "suggested" ) ) { QVariantList suggested = data[ "suggested" ].toList(); titleText = i18n( "Lyrics: Suggested URLs" ); d->showSuggested( suggested ); setCollapseOff(); } else if( data.contains( "html" ) || data.contains( "lyrics" ) ) { const bool isHtml = data.contains( QLatin1String("html") ); const QString key = isHtml ? QLatin1String("html") : QLatin1String("lyrics"); const QVariant var = data.value( key ); if( var.canConvert() ) { d->hasLyrics = true; d->browser->setRichText( isHtml ); LyricsData lyrics = var.value(); QString trimmed = lyrics.text.trimmed(); if( trimmed != d->browser->lyrics() ) { d->showLyrics( trimmed ); } else // lyrics are the same, make sure browser is showing { d->showSuggestions = false; d->showBrowser = true; } titleText = i18nc( "Lyrics: - ", "Lyrics: %1 - %2", lyrics.artist, lyrics.title ); setCollapseOff(); } } else if( data.contains( "notfound" ) || data.contains( "notFound" ) ) { titleText = i18n( "Lyrics: Not found" ); setCollapseOn(); } else { warning() << "should not be here:" << data; titleText = headerText(); } setHeaderText( titleText ); QGraphicsLinearLayout *lo = static_cast<QGraphicsLinearLayout*>( layout() ); d->showSuggestions ? lo->insertItem( 1, d->suggestView ) : lo->removeItem( d->suggestView ); d->showBrowser ? lo->addItem( d->browser ) : lo->removeItem( d->browser ); d->suggestView->setVisible( d->showSuggestions ); d->browser->setVisible( d->showBrowser ); if( !d->showSuggestions ) d->suggestView->clear(); d->determineActionIconsState(); } bool LyricsApplet::hasHeightForWidth() const { return false; } void LyricsApplet::refreshLyrics() { Q_D( LyricsApplet ); if( !d->currentTrack || !d->currentTrack->artist() ) return; if( d->hasLyrics ) { // Ask the user if he really wants to refetch the lyrics. const QString text( i18nc( "@info", "Do you really want to refetch lyrics for this track? All changes you may have made will be lost.") ); showWarning( text, SLOT(_refetchMessageButtonPressed(Plasma::MessageButton)) ); } else { // As we don't have lyrics yet we will // refetch without asking the user. d->refetchLyrics(); } } void LyricsApplet::createConfigurationInterface( KConfigDialog *parent ) { Q_D( LyricsApplet ); parent->setButtons( KDialog::Ok | KDialog::Cancel ); KConfigGroup configuration = config(); QWidget *settings = new QWidget; d->ui_settings.setupUi( settings ); d->ui_settings.fontChooser->setFont( d->browser->nativeWidget()->currentFont() ); switch( d->alignment ) { default: case Qt::AlignLeft: d->ui_settings.alignLeft->setChecked( true ); break; case Qt::AlignRight: d->ui_settings.alignRight->setChecked( true ); break; case Qt::AlignCenter: d->ui_settings.alignCenter->setChecked( true ); break; } parent->addPage( settings, i18n( "Lyrics Settings" ), "preferences-system" ); connect( parent, SIGNAL(accepted()), this, SLOT(_changeLyricsFont()) ); connect( parent, SIGNAL(accepted()), this, SLOT(_changeLyricsAlignment()) ); connect( parent, SIGNAL(applyClicked()), this, SLOT(_changeLyricsFont()) ); connect( parent, SIGNAL(applyClicked()), this, SLOT(_changeLyricsAlignment()) ); } void LyricsApplet::keyPressEvent( QKeyEvent *e ) { Q_D( LyricsApplet ); if( d->browser->nativeWidget()->isVisible() ) { bool propagate( true ); switch( e->key() ) { case Qt::Key_Escape : d->_closeLyrics(); propagate = false; break; case Qt::Key_F2 : d->_editLyrics(); propagate = false; break; } if( e->matches( QKeySequence::Save ) ) { d->_saveLyrics(); propagate = false; } if( !propagate ) { e->accept(); return; } } Context::Applet::keyPressEvent( e ); } diff --git a/src/context/applets/lyrics/LyricsSuggestionsListWidget.cpp b/src/context/applets/lyrics/LyricsSuggestionsListWidget.cpp index b188c4414b..2121723948 100644 --- a/src/context/applets/lyrics/LyricsSuggestionsListWidget.cpp +++ b/src/context/applets/lyrics/LyricsSuggestionsListWidget.cpp @@ -1,118 +1,118 @@ /**************************************************************************************** * Copyright (c) 2011 Rick W. Chen <stuffcorpse@archlinux.us> * * * * 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, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ #define DEBUG_PREFIX "LyricsSuggestionsListWidget" #include "LyricsSuggestionsListWidget.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include <KIcon> #include <KSqueezedTextLabel> #include <Plasma/IconWidget> #include <Plasma/Label> #include <Plasma/Separator> #include <QGraphicsGridLayout> #include <QGraphicsLinearLayout> #include <QGraphicsProxyWidget> LyricsSuggestionsListWidget::LyricsSuggestionsListWidget( QGraphicsWidget *parent ) : Plasma::ScrollWidget( parent ) { QGraphicsWidget *viewport = new QGraphicsWidget( this ); m_layout = new QGraphicsLinearLayout( Qt::Vertical, viewport ); setWidget( viewport ); } LyricsSuggestionsListWidget::~LyricsSuggestionsListWidget() {} void LyricsSuggestionsListWidget::add( const LyricsSuggestion &suggestion ) { QGraphicsWidget *sep = new Plasma::Separator; LyricsSuggestionItem *item = new LyricsSuggestionItem( suggestion ); item->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Maximum ); m_layout->addItem( item ); m_layout->addItem( sep ); m_items.append( item ); m_separators.append( sep ); connect( item, SIGNAL(selected(LyricsSuggestion)), SIGNAL(selected(LyricsSuggestion)) ); } void LyricsSuggestionsListWidget::clear() { qDeleteAll( m_items ); qDeleteAll( m_separators ); m_items.clear(); m_separators.clear(); } LyricsSuggestionItem::LyricsSuggestionItem( const LyricsSuggestion &suggestion, QGraphicsItem *parent ) : QGraphicsWidget( parent ) , m_data( suggestion ) { QGraphicsProxyWidget *titleProxy = new QGraphicsProxyWidget( this ); KSqueezedTextLabel *titleLabel = new KSqueezedTextLabel( m_data.title ); titleLabel->setTextElideMode( Qt::ElideRight ); titleLabel->setAttribute( Qt::WA_NoSystemBackground ); titleLabel->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred ); titleProxy->setWidget( titleLabel ); QFont font = titleLabel->font(); font.setBold( true ); titleLabel->setFont( font ); - const KUrl &url = m_data.url; + const QUrl &url = m_data.url; QString urlText = QString("<a href=\"%1\">%2</a>").arg(url.url(), url.host()); Plasma::Label *urlLabel = new Plasma::Label( this ); urlLabel->setText( urlText ); urlLabel->setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Preferred ); urlLabel->nativeWidget()->setOpenExternalLinks( true ); urlLabel->nativeWidget()->setTextInteractionFlags( Qt::TextBrowserInteraction ); urlLabel->nativeWidget()->setToolTip( url.url() ); QString artist = i18n( "artist: %1", m_data.artist ); QGraphicsProxyWidget *artistProxy = new QGraphicsProxyWidget( this ); KSqueezedTextLabel *artistLabel = new KSqueezedTextLabel( artist ); artistLabel->setTextElideMode( Qt::ElideRight ); artistLabel->setAttribute( Qt::WA_NoSystemBackground ); artistLabel->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred ); artistProxy->setWidget( artistLabel ); Plasma::IconWidget *lyricsIcon( new Plasma::IconWidget(KIcon("amarok_lyrics"), QString(), this) ); lyricsIcon->setDrawBackground( true ); connect( lyricsIcon, SIGNAL(clicked()), SLOT(onClicked()) ); QGraphicsGridLayout *layout = new QGraphicsGridLayout( this ); layout->setVerticalSpacing( 0 ); layout->addItem( lyricsIcon, 0, 0, 3, 1, Qt::AlignCenter ); layout->addItem( titleProxy, 0, 1, Qt::AlignLeft ); layout->addItem( artistProxy, 1, 1, Qt::AlignLeft ); layout->addItem( urlLabel, 2, 1, Qt::AlignLeft ); } LyricsSuggestionItem::~LyricsSuggestionItem() {} void LyricsSuggestionItem::onClicked() { emit selected( m_data ); } diff --git a/src/context/applets/lyrics/LyricsSuggestionsListWidget.h b/src/context/applets/lyrics/LyricsSuggestionsListWidget.h index db8ee87276..abe3c7cf98 100644 --- a/src/context/applets/lyrics/LyricsSuggestionsListWidget.h +++ b/src/context/applets/lyrics/LyricsSuggestionsListWidget.h @@ -1,92 +1,92 @@ /**************************************************************************************** * Copyright (c) 2011 Rick W. Chen <stuffcorpse@archlinux.us> * * * * 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, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ #ifndef LYRICS_SUGGESTIONS_LIST_WIDGET_H #define LYRICS_SUGGESTIONS_LIST_WIDGET_H -#include <KUrl> +#include <QUrl> #include <Plasma/ScrollWidget> class LyricsSuggestionItem; class QGraphicsLinearLayout; struct LyricsSuggestion { - KUrl url; + QUrl url; QString title; QString artist; }; class LyricsSuggestionsListWidget : public Plasma::ScrollWidget { Q_OBJECT public: explicit LyricsSuggestionsListWidget( QGraphicsWidget *parent = 0 ); ~LyricsSuggestionsListWidget(); void add( const LyricsSuggestion &suggestion ); void clear(); signals: void selected( const LyricsSuggestion &suggestion ); private: QList<LyricsSuggestionItem*> m_items; QList<QGraphicsWidget*> m_separators; QGraphicsLinearLayout *m_layout; Q_DISABLE_COPY( LyricsSuggestionsListWidget ) }; class LyricsSuggestionItem : public QGraphicsWidget { Q_OBJECT - Q_PROPERTY( KUrl url READ url ) + Q_PROPERTY( QUrl url READ url ) Q_PROPERTY( QString title READ title ) Q_PROPERTY( QString artist READ artist ) public: LyricsSuggestionItem( const LyricsSuggestion &suggestion, QGraphicsItem *parent = 0 ); ~LyricsSuggestionItem(); QString artist() const; QString title() const; - KUrl url() const; + QUrl url() const; signals: void selected( const LyricsSuggestion &suggestion ); private slots: void onClicked(); private: LyricsSuggestion m_data; Q_DISABLE_COPY( LyricsSuggestionItem ) }; inline QString LyricsSuggestionItem::title() const { return m_data.title; } inline QString LyricsSuggestionItem::artist() const { return m_data.artist; } -inline KUrl LyricsSuggestionItem::url() const +inline QUrl LyricsSuggestionItem::url() const { return m_data.url; } Q_DECLARE_METATYPE( LyricsSuggestion ) #endif // LYRICS_SUGGESTIONS_LIST_WIDGET_H diff --git a/src/context/applets/photos/DragPixmapItem.cpp b/src/context/applets/photos/DragPixmapItem.cpp index 2b65abc917..4c61c48bb4 100644 --- a/src/context/applets/photos/DragPixmapItem.cpp +++ b/src/context/applets/photos/DragPixmapItem.cpp @@ -1,84 +1,84 @@ /**************************************************************************************** * Copyright (c) 2009 Simon Esneault <simon.esneault@gmail.com> * * * * 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, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ #define DEBUG_PREFIX "DragPixmapItem" #include "DragPixmapItem.h" #include "core/support/Debug.h" #include <KIcon> #include <KLocale> #include <QApplication> #include <QDesktopServices> #include <QDrag> #include <QGraphicsSceneMouseEvent> #include <QMimeData> #include <QPoint> DragPixmapItem::DragPixmapItem( QGraphicsItem* parent ) : QGraphicsPixmapItem( parent ) , m_dragPos( QPoint() ) { setAcceptDrops( true ); setCursor( Qt::PointingHandCursor ); } -void DragPixmapItem::SetClickableUrl( const KUrl &url ) +void DragPixmapItem::SetClickableUrl( const QUrl &url ) { m_url = url; } void DragPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent* event) { // DEBUG_BLOCK if (event->button() == Qt::LeftButton) m_dragPos = event->pos().toPoint(); } void DragPixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { DEBUG_BLOCK if ( event->button() == Qt::LeftButton ) { if ( !m_url.isEmpty() ) { QDesktopServices::openUrl( m_url ); debug() << "DragPixmapItem: clicked photos url "<<m_url; } } } void DragPixmapItem::mouseMoveEvent( QGraphicsSceneMouseEvent* event ) { if ( !( event->buttons() & Qt::LeftButton ) ) return; if ( ( event->pos().toPoint() - m_dragPos ).manhattanLength() < QApplication::startDragDistance() ) return; QMimeData *data = new QMimeData; data->setImageData( this->pixmap().toImage() ); QDrag *drag = new QDrag( event->widget() ); drag->setMimeData( data ); drag->setPixmap( pixmap().scaledToWidth( 140 ) ); drag->setDragCursor( KIcon( "insert-image" ).pixmap( 24, 24 ), Qt::CopyAction ); drag->exec( Qt::CopyAction ); } diff --git a/src/context/applets/photos/DragPixmapItem.h b/src/context/applets/photos/DragPixmapItem.h index 6bafbf4a0b..6e21f569a2 100644 --- a/src/context/applets/photos/DragPixmapItem.h +++ b/src/context/applets/photos/DragPixmapItem.h @@ -1,60 +1,60 @@ /**************************************************************************************** * Copyright (c) 2009 Simon Esneault <simon.esneault@gmail.com> * * * * 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, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ #ifndef DRAGPIXMAPITEM_H #define DRAGPIXMAPITEM_H #include "amarok_export.h" -#include <KUrl> +#include <QUrl> #include <QGraphicsPixmapItem> //forward class QGraphicsSceneMouseEvent; /** * \brief A drag-able QGraphicsPixmapItem * * Display a pixmap which is draggable and clickable. * * \sa QGraphicsPixmapItem * * \author Simon Esneault <simon.esneault@gmail.com> */ class DragPixmapItem : public QObject, public QGraphicsPixmapItem { Q_OBJECT public: DragPixmapItem( QGraphicsItem* parent = 0 ); - void SetClickableUrl( const KUrl &url ); + void SetClickableUrl( const QUrl &url ); protected slots: /** * Reimplement mouse event */ virtual void mousePressEvent( QGraphicsSceneMouseEvent * ); virtual void mouseMoveEvent( QGraphicsSceneMouseEvent * ); virtual void mouseReleaseEvent( QGraphicsSceneMouseEvent * ); private: QPoint m_dragPos; - KUrl m_url; + QUrl m_url; }; #endif // DROPPIXMAPITEM_H diff --git a/src/context/applets/photos/PhotosScrollWidget.cpp b/src/context/applets/photos/PhotosScrollWidget.cpp index 01038c7c57..79f33b6b3b 100644 --- a/src/context/applets/photos/PhotosScrollWidget.cpp +++ b/src/context/applets/photos/PhotosScrollWidget.cpp @@ -1,512 +1,512 @@ /**************************************************************************************** * Copyright (c) 2009 Simon Esneault <simon.esneault@gmail.com> * * 2009 Nikolaj Hald Nielsen <nhn@kde.org> * * * * 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, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ #define DEBUG_PREFIX "PhotosScrollWidget" #include "PhotosScrollWidget.h" #include "DragPixmapItem.h" // Amarok #include "core/support/Amarok.h" #include "core/support/Debug.h" #include "SvgHandler.h" // QT #include <QGraphicsItem> #include <QGraphicsSceneHoverEvent> #include <QList> #include <QPixmap> #include <QPixmapCache> #include <QTimer> #include <QPropertyAnimation> PhotosScrollWidget::PhotosScrollWidget( QGraphicsItem* parent ) : QGraphicsWidget( parent ) , m_speed( 1. ) , m_margin( 5 ) , m_scrollmax( 0 ) , m_actualpos( 0 ) , m_currentPix( 0 ) , m_lastPix( 0 ) , m_interval( 3500 ) , m_mode( PHOTOS_MODE_INTERACTIVE ) , m_delta( 0 ) , m_animation( new QPropertyAnimation( this, "animValue" ) ) { setAcceptHoverEvents( true ); setFlag(QGraphicsItem::ItemClipsChildrenToShape, true); // prepare the timer for the fading effect m_timer = new QTimer( this ); m_timer->setSingleShot( true ); connect(m_timer, SIGNAL(timeout()), this, SLOT(automaticAnimBegin()) ); m_animation->setEasingCurve( QEasingCurve::Linear ); m_animation->setStartValue( 0.0 ); m_animation->setEndValue( 1.0 ); // connect the end of the animation connect( m_animation, SIGNAL(finished()), this, SLOT(automaticAnimEnd()) ); } PhotosScrollWidget::~PhotosScrollWidget() { clear(); } void PhotosScrollWidget::clear() { // DEBUG_BLOCK if( m_animation->state() == QAbstractAnimation::Running ) m_animation->stop(); // stop the timer for animation if( m_timer->isActive() ) m_timer->stop(); //delete!!! // debug() << "Going to delete " << m_pixmaplist.count() << " items"; qDeleteAll( m_pixmaplist ); m_pixmaplist.clear(); m_currentlist.clear(); m_scrollmax = 0; m_actualpos = 0; m_currentPix = 0; m_lastPix = 0; } int PhotosScrollWidget::count() const { return m_pixmaplist.count(); } void PhotosScrollWidget::setMode( int mode ) { DEBUG_BLOCK m_mode = mode; PhotosInfo::List tmp = m_currentlist; clear(); setPhotosInfoList( tmp ); tmp.clear(); } void PhotosScrollWidget::setPhotosInfoList( const PhotosInfo::List &list ) { DEBUG_BLOCK // if the list is the same, nothing happen. if( list == m_currentlist ) return; PhotosInfo::List toAddList; foreach( const PhotosInfoPtr &item, list ) { if( m_currentlist.contains( item ) ) continue; - KUrl url = item->urlphoto; + QUrl url = item->urlphoto; if( url.isValid() ) { QPixmap pixmap; if( QPixmapCache::find( url.url(), &pixmap ) ) { addPhoto( item, pixmap ); } else { m_infoHash[ url ] = item; The::networkAccessManager()->getData( url, this, - SLOT(photoFetched(KUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); + SLOT(photoFetched(QUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); } toAddList << item; } } debug() << "adding" << toAddList.count() << "new photos"; m_currentlist = toAddList; } -void PhotosScrollWidget::photoFetched( const KUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ) +void PhotosScrollWidget::photoFetched( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ) { if( !m_infoHash.contains( url ) ) return; PhotosInfoPtr info = m_infoHash.take( url ); if( e.code != QNetworkReply::NoError ) { debug() << "Error fetching photo" << e.description; return; } QPixmap pixmap; if( pixmap.loadFromData( data ) ) { QPixmapCache::insert( url.url(), pixmap ); addPhoto( info, pixmap ); } } void PhotosScrollWidget::addPhoto( const PhotosInfoPtr &item, const QPixmap &photo ) { if( photo.isNull() ) return; qreal height = 180.0 - 2 * m_margin; QPixmap pixmap = photo.scaledToHeight( height , Qt::SmoothTransformation ); pixmap = The::svgHandler()->addBordersToPixmap( pixmap, 5, QString(), true ); switch( m_mode ) { case PHOTOS_MODE_INTERACTIVE: { if( m_animation->state() == QAbstractAnimation::Running ) // careful we're animating m_animation->stop(); DragPixmapItem *dragpix = new DragPixmapItem( this ); dragpix->setPixmap( pixmap ); dragpix->setPos( m_actualpos, 0 ); dragpix->SetClickableUrl( item->urlpage ); dragpix->show(); m_pixmaplist << dragpix; int delta = dragpix->boundingRect().width() + m_margin; m_scrollmax += delta; m_actualpos += delta; emit photoAdded(); break; } case PHOTOS_MODE_AUTOMATIC: { DragPixmapItem *dragpix = new DragPixmapItem( this ); dragpix->setPixmap( pixmap ); dragpix->SetClickableUrl( item->urlpage ); // only pos and show if no animation, otherwise it will be set at the end automatically if( m_animation->state() != QAbstractAnimation::Running ) { if( !m_pixmaplist.isEmpty() ) { int x = m_pixmaplist.last()->boundingRect().width(); x += m_pixmaplist.last()->pos().x() + m_margin; dragpix->setPos( x , 0 ) ; dragpix->show(); } else { m_actualpos = 0; dragpix->setPos( m_actualpos, 0 ) ; dragpix->show(); } } m_pixmaplist << dragpix; // set a timer after and launch QTimer::singleShot( m_interval, this, SLOT(automaticAnimBegin()) ); emit photoAdded(); break; } case PHOTOS_MODE_FADING: { DragPixmapItem *dragpix = new DragPixmapItem( this ); dragpix->setPixmap( pixmap ); dragpix->setPos( (size().width() - dragpix->boundingRect().width()) / 2, 0 ); dragpix->SetClickableUrl( item->urlpage ); dragpix->hide(); m_pixmaplist << dragpix; if( m_pixmaplist.size() == 1 ) { dragpix->show(); m_timer->start( m_interval ); } emit photoAdded(); break; } } } void PhotosScrollWidget::hoverEnterEvent(QGraphicsSceneHoverEvent*) { // DEBUG_BLOCK switch ( m_mode ) { case PHOTOS_MODE_AUTOMATIC : { if( m_animation->state() == QAbstractAnimation::Running ) { m_animation->stop(); if ( m_currentPix != 0 ) m_currentPix--; } break; } } } void PhotosScrollWidget::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { // DEBUG_BLOCK switch ( m_mode ) { case PHOTOS_MODE_INTERACTIVE : { if( m_animation->state() == QAbstractAnimation::Running ) m_animation->stop(); break; } case PHOTOS_MODE_AUTOMATIC : { if( m_animation->state() == QAbstractAnimation::Running ) QTimer::singleShot( 0, this, SLOT(automaticAnimBegin()) ); break; } } } void PhotosScrollWidget::hoverMoveEvent(QGraphicsSceneHoverEvent* event) { // DEBUG_BLOCK switch ( m_mode ) { case PHOTOS_MODE_INTERACTIVE : { m_speed = ( event->pos().x() - ( size().width() / 2 ) ) / size().width(); m_speed *= 20; if( m_animation->state() == QAbstractAnimation::Running ) { m_animation->pause(); m_animation->setDuration( m_scrollmax*10 ); m_animation->resume(); } else { m_animation->setDuration( m_scrollmax*10 ); m_animation->start(); } } default: break; } } void PhotosScrollWidget::resize(qreal wid, qreal hei) { switch( m_mode ) { case PHOTOS_MODE_FADING: { foreach(DragPixmapItem *item, m_pixmaplist) { if( !item->pixmap().isNull() ) { if( size().height() != hei ) item->setPixmap( item->pixmap().scaledToHeight( (int) hei - 2 * m_margin, Qt::SmoothTransformation ) ); if( size().width() != wid ) item->setPos( ( wid - item->boundingRect().width() ) / 2, 0 ); } } break; } } QGraphicsWidget::resize( wid, hei ); } void PhotosScrollWidget::automaticAnimBegin() { if ( m_pixmaplist.size() > 1 && m_animation->state() != QAbstractAnimation::Running ) // only start if m_pixmaplist >= 2 { m_lastPix = m_currentPix; m_currentPix = ( m_currentPix + 1 ) % ( m_pixmaplist.count() ); switch( m_mode ) { case PHOTOS_MODE_AUTOMATIC: { m_delta = m_pixmaplist.at( m_currentPix )->boundingRect().width() + m_margin; if( m_animation->state() == QAbstractAnimation::Running ) m_animation->stop(); m_animation->setDuration( m_delta*20 ); m_animation->start(); break; } case PHOTOS_MODE_FADING: { if( m_animation->state() == QAbstractAnimation::Running ) m_animation->stop(); m_animation->setDuration( 1200 ); m_animation->start(); break; } default: break; } } } void PhotosScrollWidget::automaticAnimEnd() { switch( m_mode ) { case PHOTOS_MODE_AUTOMATIC: { // DEBUG_BLOCK /*if ( !m_pixmaplist.empty() && m_currentPix != 0 ) { DragPixmapItem * orgCurrentPix = m_pixmaplist.at( m_currentPix ); m_pixmaplist << m_pixmaplist.takeAt( m_lastPix ); //update index of current pic m_currentPix = m_pixmaplist.indexOf( orgCurrentPix ); m_lastPix = m_pixmaplist.count() - 1; //update to point at same pic at new position at the end of the list }*/ QTimer::singleShot( m_interval, this, SLOT(automaticAnimBegin()) ); break; } case PHOTOS_MODE_FADING: { // DEBUG_BLOCK; if ( !m_pixmaplist.empty() && m_currentPix != 0 ) { m_pixmaplist.at( m_lastPix )->hide(); } m_timer->start( m_interval ); break; } default : break; } } qreal PhotosScrollWidget::animValue() const { // Just a stub return m_delta; } void PhotosScrollWidget::animate( qreal anim ) { // DEBUG_BLOCK switch ( m_mode ) { case PHOTOS_MODE_INTERACTIVE : { // If we're are near the border and still asking to go higher ! if ( !childItems().isEmpty() && ( ( childItems().first()->pos().x() + childItems().first()->boundingRect().width() + 10 ) > boundingRect().width() ) && ( m_speed < 0 ) ) { if( m_animation->state() == QAbstractAnimation::Running ) m_animation->stop(); return; } // If we're are near the border and still asking to go down if ( !childItems().isEmpty() && ( ( childItems().last()->pos().x() - 10 ) < 0 ) && ( m_speed > 0 ) ) { if( m_animation->state() == QAbstractAnimation::Running ) m_animation->stop(); return; } int right = 0; foreach( QGraphicsItem *it, this->childItems() ) { qreal x = it->pos().x() - m_speed; it->setPos( x, it->pos().y() ); it->update(); if ( x > right ) right = x + it->boundingRect().width() + m_margin; } m_actualpos = right; break; } case PHOTOS_MODE_AUTOMATIC : { if ( !m_pixmaplist.empty() ) // just for prevention, this should never appears { if ( ( m_pixmaplist.at( m_currentPix )->pos().x() ) <= ( m_margin / 2 - 1) ) { m_actualpos = m_margin / 2 - 1; automaticAnimEnd(); return; } m_actualpos--; //this is not totally obvious, but we already made the number two visual image the current one, //so if we draw this as the first one, there will be no animation... int a = m_lastPix; int last = a - 1; if( last < 0 ) last = m_pixmaplist.count() - 1; bool first = true; int previousIndex = -1; while( true ) { int offset = m_margin; if( first ) { //we just need to move the very first image and the rest will fall in line! offset += m_actualpos; first = false; } else { offset += m_pixmaplist.at( previousIndex )->pos().x() + m_pixmaplist.at( previousIndex )->boundingRect().width(); } m_pixmaplist.at( a )->setPos( offset, m_pixmaplist.at( a )->pos().y() ); m_pixmaplist.at( a )->show(); if( a == last ) break; previousIndex = a; a = ( a + 1 ) % ( m_pixmaplist.size() ); } } break; } case PHOTOS_MODE_FADING : { if ( !m_pixmaplist.empty() ) // just for prevention, this should never appears { m_pixmaplist.at( m_lastPix )->setOpacity( 1 - anim ); m_pixmaplist.at( m_currentPix )->setOpacity( anim ); m_pixmaplist.at( m_currentPix )->show(); } break; } } } diff --git a/src/context/applets/photos/PhotosScrollWidget.h b/src/context/applets/photos/PhotosScrollWidget.h index 11baae8617..a30282f6f9 100644 --- a/src/context/applets/photos/PhotosScrollWidget.h +++ b/src/context/applets/photos/PhotosScrollWidget.h @@ -1,114 +1,114 @@ /**************************************************************************************** * Copyright (c) 2009 Simon Esneault <simon.esneault@gmail.com> * * * * 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, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ #ifndef PHOTOSSCROLLWIDGET_H #define PHOTOSSCROLLWIDGET_H #include "context/engines/photos/PhotosInfo.h" #include "NetworkAccessManagerProxy.h" #include <QGraphicsWidget> #define PHOTOS_MODE_AUTOMATIC 0 #define PHOTOS_MODE_INTERACTIVE 1 #define PHOTOS_MODE_FADING 2 //forward class QPixmap; class QPropertyAnimation; class QGraphicsSceneHoverEvent; class DragPixmapItem; /** * \brief A widget to present the photos * 3 possible animation : * - Interactive : the sliding is done on mouse hover * - Automatic : the photos are presented in an infinite loop, always scrolling * - Fading, the photos are presented one by one, fading ... * \sa QGraphicsWidget * * \author Simon Esneault <simon.esneault@gmail.com> */ class PhotosScrollWidget : public QGraphicsWidget { Q_OBJECT Q_PROPERTY(qreal animValue READ animValue WRITE animate) public: PhotosScrollWidget( QGraphicsItem* parent = 0 ); ~PhotosScrollWidget(); void setPhotosInfoList( const PhotosInfo::List &list ); void setMode( int ); void clear(); int count() const; qreal animValue() const; bool isAnimating() const; public slots: void animate( qreal anim ); void automaticAnimBegin(); void automaticAnimEnd(); /** * Reimplement resize in order to correctly repositioned the stack of pixmap */ virtual void resize( qreal, qreal ); signals: void photoAdded(); protected: /** * Reimplement mouse interaction event */ virtual void hoverMoveEvent(QGraphicsSceneHoverEvent* event); virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent* event); virtual void hoverEnterEvent(QGraphicsSceneHoverEvent* event); //virtual void keyPressEvent(QKeyEvent* event); //virtual void wheelEvent(QGraphicsSceneWheelEvent* event); private slots: - void photoFetched( const KUrl&, QByteArray, NetworkAccessManagerProxy::Error ); + void photoFetched( const QUrl&, QByteArray, NetworkAccessManagerProxy::Error ); private: void addPhoto( const PhotosInfoPtr &item, const QPixmap &photo ); float m_speed; // if negative, go to left, if positive go to right, int m_margin; // margin between the photos int m_scrollmax; // length of the whole stack int m_actualpos; // int m_currentPix; // index of the current pix int m_lastPix; // index of the lat pix int m_interval; // time in ms between to change int m_mode; // int m_delta; int m_deltastart; - QHash<KUrl, PhotosInfoPtr> m_infoHash; + QHash<QUrl, PhotosInfoPtr> m_infoHash; QPropertyAnimation *m_animation; // animation QList<int> m_timerlist; PhotosInfo::List m_currentlist; // contain the list of the current PhotosItem in the widget QList<DragPixmapItem *> m_pixmaplist; // contain the list of dragpixmap item QTimer *m_timer; // our magnificent timer }; #endif // PHOTOSSCROLLWIDGET_H diff --git a/src/context/applets/similarartists/ArtistWidget.cpp b/src/context/applets/similarartists/ArtistWidget.cpp index 24d04aaa08..f033ce1add 100644 --- a/src/context/applets/similarartists/ArtistWidget.cpp +++ b/src/context/applets/similarartists/ArtistWidget.cpp @@ -1,706 +1,706 @@ /**************************************************************************************** * Copyright (c) 2009-2010 Joffrey Clavel <jclavel@clabert.info> * * Copyright (c) 2010 Alexandre Mendes <alex.mendes1988@gmail.com> * * * * 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, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ #define DEBUG_PREFIX "ArtistWidget" #include "ArtistWidget.h" //Amarok #include "PaletteHandler.h" #include "SvgHandler.h" #include "amarokurls/AmarokUrl.h" #include "core/collections/Collection.h" #include "core/collections/QueryMaker.h" #include "core/meta/Meta.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include "core-impl/collections/support/CollectionManager.h" #include "playlist/PlaylistController.h" //KDE #include <KColorUtils> #include <KGlobalSettings> #include <KIcon> #include <Plasma/PushButton> #include <Plasma/Separator> //Qt #include <QDesktopServices> #include <QFontMetricsF> #include <QGraphicsGridLayout> #include <QGraphicsLinearLayout> #include <QLabel> #include <QPainter> #include <QPixmapCache> #include <QSignalMapper> #include <QTextDocument> #include <QTimer> #include <cmath> ArtistWidget::ArtistWidget( const SimilarArtistPtr &artist, QGraphicsWidget *parent, Qt::WindowFlags wFlags ) : QGraphicsWidget( parent, wFlags ) , m_artist( artist ) { setAttribute( Qt::WA_NoSystemBackground, true ); m_image = new QLabel; m_image->setAttribute( Qt::WA_NoSystemBackground, true ); m_image->setFixedSize( 128, 128 ); m_image->setCursor( Qt::PointingHandCursor ); QGraphicsProxyWidget *imageProxy = new QGraphicsProxyWidget( this ); imageProxy->setWidget( m_image ); m_image->installEventFilter( this ); m_nameLabel = new QLabel; m_match = new QLabel; m_tagsLabel = new QLabel; m_topTrackLabel = new QLabel; m_bio = new QGraphicsWidget( this ); QGraphicsProxyWidget *nameProxy = new QGraphicsProxyWidget( this ); QGraphicsProxyWidget *matchProxy = new QGraphicsProxyWidget( this ); QGraphicsProxyWidget *topTrackProxy = new QGraphicsProxyWidget( this ); QGraphicsProxyWidget *tagsProxy = new QGraphicsProxyWidget( this ); nameProxy->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred ); matchProxy->setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Preferred ); topTrackProxy->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred ); tagsProxy->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred ); imageProxy->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred ); nameProxy->setWidget( m_nameLabel ); matchProxy->setWidget( m_match ); topTrackProxy->setWidget( m_topTrackLabel ); tagsProxy->setWidget( m_tagsLabel ); m_nameLabel->setAttribute( Qt::WA_NoSystemBackground ); m_match->setAttribute( Qt::WA_NoSystemBackground ); m_topTrackLabel->setAttribute( Qt::WA_NoSystemBackground ); m_tagsLabel->setAttribute( Qt::WA_NoSystemBackground ); m_image->setAlignment( Qt::AlignCenter ); m_match->setAlignment( Qt::AlignRight | Qt::AlignVCenter ); m_nameLabel->setAlignment( Qt::AlignLeft | Qt::AlignVCenter ); m_topTrackLabel->setAlignment( Qt::AlignLeft | Qt::AlignVCenter ); m_tagsLabel->setAlignment( Qt::AlignLeft | Qt::AlignVCenter ); m_nameLabel->setWordWrap( false ); m_match->setWordWrap( false ); m_topTrackLabel->setWordWrap( false ); m_tagsLabel->setWordWrap( false ); m_match->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred ); m_topTrackLabel->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred ); m_match->setMinimumWidth( 10 ); m_topTrackLabel->setMinimumWidth( 10 ); m_nameLabel->setMinimumWidth( 10 ); m_tagsLabel->setMinimumWidth( 10 ); QFontMetricsF fm( font() ); m_bio->setMinimumHeight( fm.lineSpacing() * 5 ); m_bio->setMaximumHeight( fm.lineSpacing() * 5 ); m_bio->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred ); m_bioLayout.setCacheEnabled( true ); QFont artistFont; artistFont.setPointSize( artistFont.pointSize() + 2 ); artistFont.setBold( true ); m_nameLabel->setFont( artistFont ); m_topTrackLabel->setFont( KGlobalSettings::smallestReadableFont() ); m_tagsLabel->setFont( KGlobalSettings::smallestReadableFont() ); m_match->setFont( KGlobalSettings::smallestReadableFont() ); m_navigateButton = new Plasma::PushButton( this ); m_navigateButton->setMaximumSize( QSizeF( 22, 22 ) ); m_navigateButton->setIcon( KIcon( "edit-find" ) ); m_navigateButton->setToolTip( i18n( "Show in Media Sources" ) ); connect( m_navigateButton, SIGNAL(clicked()), this, SLOT(navigateToArtist()) ); m_lastfmStationButton = new Plasma::PushButton( this ); m_lastfmStationButton->setMaximumSize( QSizeF( 22, 22 ) ); m_lastfmStationButton->setIcon( KIcon("view-services-lastfm-amarok") ); m_lastfmStationButton->setToolTip( i18n( "Add Last.fm artist station to the Playlist" ) ); connect( m_lastfmStationButton, SIGNAL(clicked()), this, SLOT(addLastfmArtistStation()) ); m_topTrackButton = new Plasma::PushButton( this ); m_topTrackButton->setMaximumSize( QSizeF( 22, 22 ) ); m_topTrackButton->setIcon( KIcon( "media-track-add-amarok" ) ); m_topTrackButton->setToolTip( i18n( "Add top track to the Playlist" ) ); m_topTrackButton->hide(); connect( m_topTrackButton, SIGNAL(clicked()), this, SLOT(addTopTrackToPlaylist()) ); m_similarArtistButton = new Plasma::PushButton( this ); m_similarArtistButton->setMaximumSize( QSizeF( 22, 22 ) ); m_similarArtistButton->setIcon( KIcon( "similarartists-amarok" ) ); m_similarArtistButton->setToolTip( i18n( "Show Similar Artists to %1", m_artist->name() ) ); connect( m_similarArtistButton, SIGNAL(clicked()), this, SIGNAL(showSimilarArtists()) ); QGraphicsLinearLayout *buttonsLayout = new QGraphicsLinearLayout( Qt::Horizontal ); buttonsLayout->setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Preferred ); buttonsLayout->addItem( m_topTrackButton ); buttonsLayout->addItem( m_navigateButton ); buttonsLayout->addItem( m_lastfmStationButton ); QString artistUrl = m_artist->url().url(); if( !artistUrl.isEmpty() ) { m_urlButton = new Plasma::PushButton( this ); m_urlButton->setMaximumSize( QSizeF( 22, 22 ) ); m_urlButton->setIcon( KIcon("applications-internet") ); m_urlButton->setToolTip( i18n( "Open Last.fm webpage for this artist" ) ); connect( m_urlButton, SIGNAL(clicked()), this, SLOT(openArtistUrl()) ); buttonsLayout->addItem( m_urlButton ); } buttonsLayout->addItem( m_similarArtistButton ); // the image display is extended on two row m_layout = new QGraphicsGridLayout( this ); m_layout->addItem( imageProxy, 0, 0, 4, 1 ); m_layout->addItem( nameProxy, 0, 1 ); m_layout->addItem( buttonsLayout, 0, 2, Qt::AlignRight ); m_layout->addItem( topTrackProxy, 1, 1 ); m_layout->addItem( matchProxy, 1, 2, Qt::AlignRight ); m_layout->addItem( tagsProxy, 2, 1, 1, 2 ); m_layout->addItem( m_bio, 3, 1, 1, 2 ); m_match->setText( i18n( "Match: %1%", QString::number( m_artist->match() ) ) ); m_nameLabel->setText( m_artist->name() ); QTimer::singleShot( 0, this, SLOT(updateInfo()) ); } void ArtistWidget::updateInfo() { fetchPhoto(); fetchInfo(); fetchTopTrack(); } ArtistWidget::~ArtistWidget() { clear(); } bool ArtistWidget::eventFilter( QObject *obj, QEvent *event ) { if( obj == m_image ) { if( event->type() == QEvent::MouseButtonPress ) { emit showBio(); event->accept(); return true; } } return QGraphicsWidget::eventFilter( obj, event ); } void ArtistWidget::fetchPhoto() { // display a message for the user while the fetch of the picture m_image->clear(); QPixmap image; if( QPixmapCache::find( m_artist->urlImage().url(), &image ) ) { m_image->setPixmap( image ); return; } m_image->setPixmap( Amarok::semiTransparentLogo( 120 ) ); if( m_artist->urlImage().isEmpty() ) return; The::networkAccessManager()->getData( m_artist->urlImage(), this, - SLOT(photoFetched(KUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); + SLOT(photoFetched(QUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); } void ArtistWidget::fetchInfo() { // we genere the url for the demand on the lastFM Api - KUrl url; + QUrl url; url.setScheme( "http" ); url.setHost( "ws.audioscrobbler.com" ); url.setPath( "/2.0/" ); url.addQueryItem( "method", "artist.getInfo" ); url.addQueryItem( "api_key", Amarok::lastfmApiKey() ); url.addQueryItem( "artist", m_artist->name() ); The::networkAccessManager()->getData( url, this, - SLOT(parseInfo(KUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); + SLOT(parseInfo(QUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); } void ArtistWidget::fetchTopTrack() { // we genere the url for the demand on the lastFM Api - KUrl url; + QUrl url; url.setScheme( "http" ); url.setHost( "ws.audioscrobbler.com" ); url.setPath( "/2.0/" ); url.addQueryItem( "method", "artist.getTopTracks" ); url.addQueryItem( "api_key", Amarok::lastfmApiKey() ); url.addQueryItem( "artist", m_artist->name() ); The::networkAccessManager()->getData( url, this, - SLOT(parseTopTrack(KUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); + SLOT(parseTopTrack(QUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); } void -ArtistWidget::photoFetched( const KUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ) +ArtistWidget::photoFetched( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ) { if( url != m_artist->urlImage() ) return; if( e.code != QNetworkReply::NoError ) { m_image->clear(); m_image->setText( i18n( "Unable to fetch the picture: %1", e.description ) ); return; } QPixmap image; if( image.loadFromData( data ) ) { image = image.scaled( 116, 116, Qt::KeepAspectRatio, Qt::SmoothTransformation ); image = The::svgHandler()->addBordersToPixmap( image, 6, QString(), true ); m_image->setToolTip( i18nc( "@info:tooltip Artist biography", "Show Biography" ) ); m_image->setPixmap( image ); QPixmapCache::insert( url.url(), image ); } } void -ArtistWidget::parseInfo( const KUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ) +ArtistWidget::parseInfo( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ) { Q_UNUSED( url ) if( e.code != QNetworkReply::NoError ) return; if( data.isEmpty() ) return; QXmlStreamReader xml( data ); xml.readNextStartElement(); // lfm if( xml.attributes().value(QLatin1String("status")) != QLatin1String("ok") ) { setBioSummary( QString() ); return; } QString summary; xml.readNextStartElement(); // artist while( xml.readNextStartElement() ) { if( xml.name() == QLatin1String("tags") ) { m_tags.clear(); while( xml.readNextStartElement() ) { if( xml.name() != QLatin1String("tag") ) continue; while( xml.readNextStartElement() ) { if( xml.name() == QLatin1String("name") ) m_tags << xml.readElementText(); else xml.skipCurrentElement(); } } } else if( xml.name() == QLatin1String("bio") ) { while( xml.readNextStartElement() ) { if( xml.name() == QLatin1String("published") ) m_fullBio.first = KDateTime::fromString( xml.readElementText(), "%a, %d %b %Y %H:%M:%S" ); else if( xml.name() == QLatin1String("summary") ) summary = xml.readElementText().simplified(); else if( xml.name() == QLatin1String("content") ) m_fullBio.second = xml.readElementText().replace( QRegExp("\n+"), QLatin1String("<br>") ); else xml.skipCurrentElement(); } } else xml.skipCurrentElement(); } setBioSummary( summary ); setTags(); } void -ArtistWidget::parseTopTrack( const KUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ) +ArtistWidget::parseTopTrack( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ) { Q_UNUSED( url ) if( e.code != QNetworkReply::NoError ) return; if( data.isEmpty() ) return; QXmlStreamReader xml( data ); xml.readNextStartElement(); // lfm if( xml.attributes().value(QLatin1String("status")) != QLatin1String("ok") ) { setTopTrack( QString() ); return; } QString topTrack; xml.readNextStartElement(); // toptracks while( xml.readNextStartElement() ) { if( xml.name() != QLatin1String("track") ) { xml.skipCurrentElement(); continue; } while( xml.readNextStartElement() ) { if( xml.name() != QLatin1String("name") ) { xml.skipCurrentElement(); continue; } topTrack = xml.readElementText(); break; } if( !topTrack.isEmpty() ) break; } setTopTrack( topTrack ); } SimilarArtistPtr ArtistWidget::artist() const { return m_artist; } void ArtistWidget::clear() { m_image->clear(); m_nameLabel->clear(); m_match->clear(); m_topTrackLabel->clear(); } void ArtistWidget::openArtistUrl() { // somehow Last.fm decides to supply this url without the scheme - KUrl artistUrl = QString( "http://%1" ).arg( m_artist->url().url() ); + QUrl artistUrl = QString( "http://%1" ).arg( m_artist->url().url() ); if( artistUrl.isValid() ) QDesktopServices::openUrl( artistUrl ); } void ArtistWidget::setBioSummary( const QString &bio ) { if( bio.isEmpty() ) { m_bioLayout.clearLayout(); m_bioLayout.setText( i18n( "No description available." ) ); } else { QTextDocument doc; doc.setHtml( bio ); QString plain = doc.toPlainText(); m_bioLayout.setText( plain ); } layoutBio(); } void ArtistWidget::setTags() { QString tags = m_tags.isEmpty() ? i18n( "none" ) : m_tags.join( QLatin1String(", ") ); QString label = i18nc( "@label:textbox", "Tags: %1", tags ); m_tagsLabel->setText( label ); } void ArtistWidget::setTopTrack( const QString &topTrack ) { if( topTrack.isEmpty() ) { m_topTrackLabel->setText( i18n("Top track not found") ); m_topTrackButton->hide(); } else { m_topTrackTitle = topTrack; m_topTrackLabel->setText( i18n("Top track: %1", topTrack) ); Collections::QueryMaker *qm = CollectionManager::instance()->queryMaker(); qm->setQueryType( Collections::QueryMaker::Track ); qm->beginAnd(); qm->addFilter( Meta::valArtist, m_nameLabel->text() ); qm->addFilter( Meta::valTitle, m_topTrackTitle ); qm->endAndOr(); qm->limitMaxResultSize( 1 ); qm->setAutoDelete( true ); connect( qm, SIGNAL(newResultReady(Meta::TrackList)), SLOT(resultReady(Meta::TrackList)) ); qm->run(); } } void ArtistWidget::resizeEvent( QGraphicsSceneResizeEvent *event ) { QGraphicsWidget::resizeEvent( event ); layoutBio(); QFontMetrics fm( m_match->font() ); m_match->setMaximumWidth( fm.width( m_match->text() ) ); } void ArtistWidget::paint( QPainter *p, const QStyleOptionGraphicsItem *option, QWidget *widget ) { QGraphicsWidget::paint( p, option, widget ); p->save(); QFontMetricsF fm( m_bio->font() ); QPointF pos = m_bio->geometry().topLeft(); const int maxLines = floor( m_bio->size().height() / fm.lineSpacing() ); for( int i = 0, lines = m_bioLayout.lineCount(); i < lines; ++i ) { const QTextLine &line = m_bioLayout.lineAt( i ); if( m_bioCropped && (i == (maxLines - 1)) ) { // fade out the last bit of text if not all of the bio is shown QLinearGradient alphaGradient( 0, 0, 1, 0 ); alphaGradient.setCoordinateMode( QGradient::ObjectBoundingMode ); const QColor &textColor = The::paletteHandler()->palette().text().color(); alphaGradient.setColorAt( 0, textColor ); alphaGradient.setColorAt( 0.85, textColor ); alphaGradient.setColorAt( 1, Qt::transparent ); QPen pen = p->pen(); pen.setBrush( alphaGradient ); p->setPen( pen ); } line.draw( p, pos ); pos.ry() += line.leading(); } p->restore(); } void ArtistWidget::layoutBio() { QFontMetricsF fm( m_bio->font() ); QRectF geom = m_bio->geometry(); int maxLines = floor( m_bio->size().height() / fm.lineSpacing() ); int leading = fm.leading(); qreal height = 0; m_bioCropped = true; m_bioLayout.clearLayout(); m_bioLayout.beginLayout(); while( m_bioLayout.lineCount() < maxLines ) { QTextLine line = m_bioLayout.createLine(); if( !line.isValid() ) { m_bioCropped = false; break; } line.setLineWidth( geom.width() ); height += leading; line.setPosition( QPointF(0, height) ); height += line.height(); } m_bioLayout.endLayout(); update(); } KDateTime ArtistWidget::bioPublished() const { return m_fullBio.first; } QString ArtistWidget::fullBio() const { return m_fullBio.second; } void ArtistWidget::addTopTrackToPlaylist() { The::playlistController()->insertOptioned( m_topTrack, Playlist::OnAppendToPlaylistAction ); } void ArtistWidget::navigateToArtist() { AmarokUrl url; url.setCommand( "navigate" ); url.setPath( "collections" ); url.setArg( "filter", "artist:\"" + AmarokUrl::escape( m_artist->name() ) + '\"' ); url.run(); } void ArtistWidget::addLastfmArtistStation() { const QString url = "lastfm://artist/" + m_artist->name() + "/similarartists"; - Meta::TrackPtr lastfmtrack = CollectionManager::instance()->trackForUrl( KUrl( url ) ); + Meta::TrackPtr lastfmtrack = CollectionManager::instance()->trackForUrl( QUrl( url ) ); The::playlistController()->insertOptioned( lastfmtrack, Playlist::OnAppendToPlaylistAction ); } ArtistsListWidget::ArtistsListWidget( QGraphicsWidget *parent ) : Plasma::ScrollWidget( parent ) , m_separatorCount( 0 ) { QGraphicsWidget *content = new QGraphicsWidget( this ); m_layout = new QGraphicsLinearLayout( Qt::Vertical, content ); setWidget( content ); m_showArtistsSigMapper = new QSignalMapper( this ); connect( m_showArtistsSigMapper, SIGNAL(mapped(QString)), SIGNAL(showSimilarArtists(QString)) ); m_showBioSigMapper = new QSignalMapper( this ); connect( m_showBioSigMapper, SIGNAL(mapped(QString)), SIGNAL(showBio(QString)) ); } ArtistsListWidget::~ArtistsListWidget() { clear(); } int ArtistsListWidget::count() const { return m_layout->count() - m_separatorCount; } void ArtistsListWidget::addArtist( const SimilarArtistPtr &artist ) { if( !m_widgets.isEmpty() ) addSeparator(); ArtistWidget *widget = new ArtistWidget( artist ); const QString &name = artist->name(); connect( widget, SIGNAL(showSimilarArtists()), m_showArtistsSigMapper, SLOT(map()) ); m_showArtistsSigMapper->setMapping( widget, name ); connect( widget, SIGNAL(showBio()), m_showBioSigMapper, SLOT(map()) ); m_showBioSigMapper->setMapping( widget, name ); m_layout->addItem( widget ); m_widgets << widget; } void ArtistsListWidget::addArtists( const SimilarArtist::List &artists ) { foreach( const SimilarArtistPtr &artist, artists ) addArtist( artist ); updateGeometry(); } void ArtistsListWidget::addSeparator() { m_layout->addItem( new Plasma::Separator ); ++m_separatorCount; } void ArtistsListWidget::clear() { qDeleteAll( m_widgets ); m_widgets.clear(); int count = m_layout->count(); if( count > 0 ) { while( --count >= 0 ) { QGraphicsLayoutItem *child = m_layout->itemAt( 0 ); m_layout->removeItem( child ); delete child; } m_separatorCount = 0; } m_layout->invalidate(); updateGeometry(); } QSizeF ArtistsListWidget::sizeHint( Qt::SizeHint which, const QSizeF &constraint ) const { QSizeF sz = Plasma::ScrollWidget::sizeHint( which, constraint ); if( count() == 0 ) sz.rheight() = 0; return sz; } bool ArtistsListWidget::isEmpty() const { return count() == 0; } QString ArtistsListWidget::name() const { return m_name; } void ArtistsListWidget::setName( const QString &name ) { m_name = name; } ArtistWidget * ArtistsListWidget::widget( const QString &artistName ) { foreach( ArtistWidget *widget, m_widgets ) { if( widget->artist()->name() == artistName ) return widget; } return 0; } void ArtistWidget::resultReady( const Meta::TrackList &tracks ) { if( !tracks.isEmpty() ) { m_topTrack = tracks.first(); m_topTrackButton->show(); } } diff --git a/src/context/applets/similarartists/ArtistWidget.h b/src/context/applets/similarartists/ArtistWidget.h index c819d06d18..4999065774 100644 --- a/src/context/applets/similarartists/ArtistWidget.h +++ b/src/context/applets/similarartists/ArtistWidget.h @@ -1,309 +1,309 @@ /**************************************************************************************** * Copyright (c) 2009-2010 Joffrey Clavel <jclavel@clabert.info> * * Copyright (c) 2010 Alexandre Mendes <alex.mendes1988@gmail.com> * * * * 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, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ #ifndef ARTIST_WIDGET_H #define ARTIST_WIDGET_H #include "core/meta/forward_declarations.h" #include "network/NetworkAccessManagerProxy.h" #include "SimilarArtist.h" -#include <KUrl> +#include <QUrl> #include <KDateTime> #include <Plasma/ScrollWidget> #include <QTextLayout> class QGraphicsGridLayout; class QGraphicsLinearLayout; class QSignalMapper; class QLabel; namespace Plasma { class PushButton; } /** * A widget for display an artist with some details * @author Joffrey Clavel * @version 0.2 */ class ArtistWidget : public QGraphicsWidget { Q_OBJECT Q_PROPERTY( KDateTime bioPublished READ bioPublished ) Q_PROPERTY( QString fullBio READ fullBio ) public: /** * ArtistWidget constructor * @param parent The widget parent */ ArtistWidget( const SimilarArtistPtr &artist, QGraphicsWidget *parent = 0, Qt::WindowFlags wFlags = 0 ); /** * ArtistWidget destructor */ ~ArtistWidget(); virtual void paint( QPainter *p, const QStyleOptionGraphicsItem *option, QWidget *widget = 0 ); /** * Pointer to the similar artist this widget is associated with */ SimilarArtistPtr artist() const; /** * Clean the widget => the content of the QLabel is empty */ void clear(); /** * The date/time the bio was published * @return date/time */ KDateTime bioPublished() const; /** * Complete bio of artist on last.fm * @return bio of artist */ QString fullBio() const; /** * Change the most known track of this artist * @param topTrack the top track of this artist */ void setTopTrack( const QString &topTrack ); bool eventFilter( QObject *obj, QEvent *event ); signals: /** * Show similar artists to the artist associated with this widget */ void showSimilarArtists(); /** * Show full bio of artist */ void showBio(); protected: void resizeEvent( QGraphicsSceneResizeEvent *event ); private: void fetchPhoto(); //!< Fetch the photo of the artist void fetchInfo(); //!< Fetch the artist info void fetchTopTrack(); //!< Fetch the artist'stop track /** * Set the artist bio summary * @param bio The bio of this artist */ void setBioSummary( const QString &bio ); /** * Set arist tags */ void setTags(); /** * Layout the text for artist's bio summary */ void layoutBio(); /** * Layout for the formatting of the widget contents */ QGraphicsGridLayout *m_layout; /** * Image of the artist */ QLabel *m_image; /** * Label showing the name of the artist */ QLabel *m_nameLabel; /** * Similarity match percentage */ QLabel *m_match; /** * Title of the top track */ QString m_topTrackTitle; /** * Label showing the title of the top track of the artist */ QLabel *m_topTrackLabel; /** * Label showing artist tags */ QLabel *m_tagsLabel; /** * Meta::LabelPtr to the top track, if it's in a collection */ Meta::TrackPtr m_topTrack; /** * Button to add the top track to the playlist */ Plasma::PushButton *m_topTrackButton; /** * Button to add the last.fm simmilar artist station for this artist to the playlist */ Plasma::PushButton *m_lastfmStationButton; /** * Button to navigate to the artit in the local collection */ Plasma::PushButton *m_navigateButton; /** * Button to open Last.fm's artist webpage using external browser */ Plasma::PushButton *m_urlButton; /** * Button to show similar artists to the artist associated with this widget */ Plasma::PushButton *m_similarArtistButton; /** * Bio summary of the artist */ QGraphicsWidget *m_bio; /** * Text layout for the artist bio */ QTextLayout m_bioLayout; /** * Whether all of artist bio is shown */ bool m_bioCropped; /** * The complete bio with its published date */ QPair<KDateTime, QString> m_fullBio; /** * List of artist tags */ QStringList m_tags; const SimilarArtistPtr m_artist; private slots: /** * Handle artist photo retrieved from Last.fm */ - void photoFetched( const KUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ); + void photoFetched( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ); /** * Parse the xml fetched on the lastFM API for the artist info. * Launched when the download of the data are finished and for each similarArtists. */ - void parseInfo( const KUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ); + void parseInfo( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ); /** * Parse the xml fetched on the lastFM API for the similarArtist most known track. * Launched when the download of the data are finished and for each similarArtists. */ - void parseTopTrack( const KUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ); + void parseTopTrack( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ); /** * Open an URL * @param url The URL of the artist */ void openArtistUrl(); /** * Add top track to the playlist */ void addTopTrackToPlaylist(); /** * Navigate to this artist in the local collection */ void navigateToArtist(); /** * Add this artists last.fm similar artist stream */ void addLastfmArtistStation(); /** * Get results from the query maker */ void resultReady( const Meta::TrackList &tracks ); void updateInfo(); }; class ArtistsListWidget : public Plasma::ScrollWidget { Q_OBJECT Q_PROPERTY( QString name READ name WRITE setName ) public: explicit ArtistsListWidget( QGraphicsWidget *parent = 0 ); ~ArtistsListWidget(); int count() const; bool isEmpty() const; void addArtists( const SimilarArtist::List &artists ); QString name() const; void setName( const QString &name ); void clear(); ArtistWidget *widget( const QString &artistName ); QSizeF sizeHint( Qt::SizeHint which, const QSizeF &constraint = QSizeF() ) const; signals: void showSimilarArtists( const QString &artist ); void showBio( const QString &artist ); private: void addArtist( const SimilarArtistPtr &artist ); void addSeparator(); int m_separatorCount; QString m_name; QGraphicsLinearLayout *m_layout; QSignalMapper *m_showArtistsSigMapper; QSignalMapper *m_showBioSigMapper; QList<ArtistWidget*> m_widgets; Q_DISABLE_COPY( ArtistsListWidget ) }; #endif // ARTIST_WIDGET_H diff --git a/src/context/applets/similarartists/SimilarArtist.cpp b/src/context/applets/similarartists/SimilarArtist.cpp index eaa5db665a..e26513d770 100644 --- a/src/context/applets/similarartists/SimilarArtist.cpp +++ b/src/context/applets/similarartists/SimilarArtist.cpp @@ -1,131 +1,131 @@ /**************************************************************************************** * Copyright (c) 2009-2010 Joffrey Clavel <jclavel@clabert.info> * * * * 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, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ #include "SimilarArtist.h" #include <QXmlStreamReader> SimilarArtist::SimilarArtist() {} -SimilarArtist::SimilarArtist( const QString &name, const int match, const KUrl &url, - const KUrl &urlImage, const QString &similarTo ) +SimilarArtist::SimilarArtist( const QString &name, const int match, const QUrl &url, + const QUrl &urlImage, const QString &similarTo ) : m_name( name ) , m_match( match ) , m_url( url ) , m_urlImage( urlImage ) , m_similarTo( similarTo ) { static bool metaTypeRegistered = false; if ( !metaTypeRegistered ) { qRegisterMetaType<SimilarArtist>( "SimilarArtists" ); metaTypeRegistered = true; } } SimilarArtist::SimilarArtist( const SimilarArtist &other ) : QSharedData( other ) , m_name( other.m_name ) , m_match( other.m_match ) , m_url( other.m_url ) , m_urlImage( other.m_urlImage ) , m_similarTo( other.m_similarTo ) { } QString SimilarArtist::name() const { return m_name; } int SimilarArtist::match() const { return m_match; } -KUrl +QUrl SimilarArtist::url() const { return m_url; } -KUrl +QUrl SimilarArtist::urlImage() const { return m_urlImage; } QString SimilarArtist::similarTo() const { return m_similarTo; } void SimilarArtist::setSimilarTo( const QString &artist ) { m_similarTo = artist; } SimilarArtist::List SimilarArtist::listFromXml( QXmlStreamReader &xml ) { SimilarArtist::List saList; xml.readNextStartElement(); // lfm if( xml.attributes().value(QLatin1String("status")) != QLatin1String("ok") ) return saList; QString similarTo; xml.readNextStartElement(); // similarartists if( xml.attributes().hasAttribute(QLatin1String("artist")) ) similarTo = xml.attributes().value(QLatin1String("artist")).toString(); while( xml.readNextStartElement() ) { if( xml.name() == QLatin1String("artist") ) { QString name; - KUrl artistUrl; - KUrl imageUrl; + QUrl artistUrl; + QUrl imageUrl; float match( 0.0 ); while( xml.readNextStartElement() ) { const QStringRef &n = xml.name(); const QXmlStreamAttributes &a = xml.attributes(); if( n == QLatin1String("name") ) name = xml.readElementText(); else if( n == QLatin1String("match") ) match = xml.readElementText().toFloat() * 100.0; else if( n == QLatin1String("url") ) - artistUrl = KUrl( xml.readElementText() ); + artistUrl = QUrl( xml.readElementText() ); else if( n == QLatin1String("image") && a.hasAttribute(QLatin1String("size")) && a.value(QLatin1String("size")) == QLatin1String("large") ) - imageUrl = KUrl( xml.readElementText() ); + imageUrl = QUrl( xml.readElementText() ); else xml.skipCurrentElement(); } SimilarArtistPtr artist( new SimilarArtist( name, match, artistUrl, imageUrl, similarTo ) ); saList.append( artist ); } else xml.skipCurrentElement(); } return saList; } diff --git a/src/context/applets/similarartists/SimilarArtist.h b/src/context/applets/similarartists/SimilarArtist.h index ee2b09a377..072e1acf9f 100644 --- a/src/context/applets/similarartists/SimilarArtist.h +++ b/src/context/applets/similarartists/SimilarArtist.h @@ -1,125 +1,125 @@ /**************************************************************************************** * Copyright (c) 2009-2010 Joffrey Clavel <jclavel@clabert.info> * * * * 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, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ #ifndef SIMILAR_ARTIST_H #define SIMILAR_ARTIST_H //Kde #include <KSharedPtr> -#include <KUrl> +#include <QUrl> //Qt #include <QSharedData> #include <QString> #include <QXmlStreamReader> class SimilarArtist; typedef KSharedPtr<SimilarArtist> SimilarArtistPtr; /** * Represents a similar artist to another * @author Joffrey Clavel * @version 0.1 */ class SimilarArtist : public QSharedData { public: typedef QList<SimilarArtistPtr> List; /** * Create an empty similar artist */ SimilarArtist(); /** * Create a similar artist with data * @param name The name of this similar artist * @param match The match pourcent (between 0 and 100) of the similarity * between this artist and the artist similarTo * @param url A url of this artist on the web, for example on last.fm * @param urlImage A url of an image of this artist, for example on last.fm * @param similarTo The name of the artist similar to this artist */ - SimilarArtist( const QString &name, const int match, const KUrl &url, - const KUrl &urlImage, const QString &similarTo ); + SimilarArtist( const QString &name, const int match, const QUrl &url, + const QUrl &urlImage, const QString &similarTo ); SimilarArtist( const SimilarArtist &other ); /** * @return The name of this artist */ QString name() const; /** * @return the pourcent of match of this artist, betwwen 0 and 100 */ int match() const; /** * @return a url on the web for this artist, for example on last.fm */ - KUrl url() const; + QUrl url() const; /** * @return a url on the web for an image oh this artist, for example on last.fm */ - KUrl urlImage() const; + QUrl urlImage() const; /** * @return the artist this similar artist is related to */ QString similarTo() const; /** * Set the artist this similar artist is related to * @param artist artist name */ void setSimilarTo( const QString &artist ); static SimilarArtist::List listFromXml( QXmlStreamReader &xml ); private: /** * The name of this artist */ QString m_name; /** * The match of this artist to the artist similarTo, between 0 and 100 */ int m_match; /** * A url of this artist on the web */ - KUrl m_url; + QUrl m_url; /** * A image url of this artist on the web */ - KUrl m_urlImage; + QUrl m_urlImage; /** * The name of the artist similar to this artist */ QString m_similarTo; }; Q_DECLARE_METATYPE( SimilarArtist ) Q_DECLARE_METATYPE( SimilarArtistPtr ) Q_DECLARE_METATYPE( SimilarArtist::List ) #endif // SIMILAR_ARTIST_H diff --git a/src/context/applets/upcomingevents/LastFmEvent.cpp b/src/context/applets/upcomingevents/LastFmEvent.cpp index de0df93ef5..b6f0c9e653 100644 --- a/src/context/applets/upcomingevents/LastFmEvent.cpp +++ b/src/context/applets/upcomingevents/LastFmEvent.cpp @@ -1,144 +1,144 @@ /**************************************************************************************** * Copyright (c) 2009 Nathan Sala <sala.nathan@gmail.com> * * Copyright (c) 2009-2010 Ludovic Deveaux <deveaux.ludovic31@gmail.com> * * * * 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, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ #include "LastFmEvent.h" LastFmEvent::LastFmEvent() { static bool metaTypeRegistered = false; if (!metaTypeRegistered) { qRegisterMetaType<LastFmEvent>("LastFmEvent"); metaTypeRegistered = true; } } LastFmEvent::LastFmEvent( const LastFmEvent& event) : QSharedData( event ) , m_attendance( event.m_attendance ) , m_cancelled( event.m_cancelled ) , m_date( event.m_date ) , m_url( event.m_url ) , m_imageUrls( event.m_imageUrls ) , m_description( event.m_description ) , m_name( event.m_name ) , m_headliner( event.m_headliner ) , m_participants( event.m_participants ) , m_tags( event.m_tags ) , m_venue( event.m_venue ) {} LastFmEvent::~LastFmEvent() {} QStringList LastFmEvent::artists() const { return QStringList() << m_headliner << m_participants; } KDateTime LastFmEvent::date() const { return m_date; } QString LastFmEvent::name() const { return m_name; } -KUrl LastFmEvent::url() const +QUrl LastFmEvent::url() const { return m_url; } void LastFmEvent::setDate( const KDateTime &date ) { m_date = date; } void LastFmEvent::setName( const QString &name ) { m_name = name; } -void LastFmEvent::setUrl( const KUrl &url ) +void LastFmEvent::setUrl( const QUrl &url ) { m_url = url; } QString LastFmEvent::imageSizeToString( ImageSize size ) { switch( size ) { default: case Small: return QString("small"); case Medium: return QString("medium"); case Large: return QString("large"); case ExtraLarge: return QString("extralarge"); case Mega: return QString("maga"); } } LastFmEvent::ImageSize LastFmEvent::stringToImageSize( const QString &string ) { if( string == "small" ) return Small; if( string == "medium" ) return Medium; if( string == "large" ) return Large; if( string == "extralarge" ) return ExtraLarge; if( string == "mega" ) return Mega; return Small; } LastFmLocation::LastFmLocation() {} LastFmLocation::~LastFmLocation() {} LastFmLocation::LastFmLocation( const LastFmLocation &cpy ) : QSharedData( cpy ) , city( cpy.city ) , country( cpy.country ) , street( cpy.street ) , postalCode( cpy.postalCode ) , latitude( cpy.latitude ) , longitude( cpy.longitude ) {} LastFmVenue::LastFmVenue() {} LastFmVenue::~LastFmVenue() {} LastFmVenue::LastFmVenue( const LastFmVenue &cpy ) : QSharedData( cpy ) , id( cpy.id ) , name( cpy.name ) , url( cpy.url ) , website( cpy.website ) , phoneNumber( cpy.phoneNumber ) , imageUrls( cpy.imageUrls ) , location( cpy.location ) {} diff --git a/src/context/applets/upcomingevents/LastFmEvent.h b/src/context/applets/upcomingevents/LastFmEvent.h index 50c06bc84f..0ffe602ea5 100644 --- a/src/context/applets/upcomingevents/LastFmEvent.h +++ b/src/context/applets/upcomingevents/LastFmEvent.h @@ -1,290 +1,290 @@ /**************************************************************************************** * Copyright (c) 2009 Nathan Sala <sala.nathan@gmail.com> * * Copyright (c) 2009-2010 Ludovic Deveaux <deveaux.ludovic31@gmail.com> * * * * 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, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ #ifndef LASTFMEVENT_H #define LASTFMEVENT_H #include <KDateTime> #include <KSharedPtr> -#include <KUrl> +#include <QUrl> #include <QSharedData> #include <QStringList> class LastFmEvent; class LastFmLocation; class LastFmVenue; typedef KSharedPtr<LastFmEvent> LastFmEventPtr; typedef KSharedPtr<LastFmVenue> LastFmVenuePtr; typedef KSharedPtr<LastFmLocation> LastFmLocationPtr; /** * A class to store an event fetched from the last.fm API * by the request artist.getEvents */ class LastFmEvent : public QSharedData { public: enum ImageSize { Small = 0, Medium = 1, Large = 2, ExtraLarge = 3, Mega = 4 }; typedef QList< LastFmEventPtr > List; - typedef QHash<ImageSize, KUrl> ImageUrls; + typedef QHash<ImageSize, QUrl> ImageUrls; /** * Creates an empty LastFmEvent */ LastFmEvent(); /** * Creates a copy of a LastFmEvent * @param event the event to be copied from */ LastFmEvent( const LastFmEvent& ); /** * Destroys a LastFmEvent instance */ ~LastFmEvent(); /** * A getter for the artists list. * It consists of the headliner + participants. * @return the list of all artists participating the event */ QStringList artists() const; /** * The number of people attending the event * @return number of people attending the event */ int attendance() const { return m_attendance; } /** * A getter for the event's date * @return the event's date */ KDateTime date() const; /** * The event's description * @return event's description */ QString description() const { return m_description; } /** * The event's headlining artist * @return event's headlining artist */ QString headliner() const { return m_headliner; } /** * Gets the URL for the event's event at \p size; * @param size size of the image * @return image URL */ - KUrl imageUrl( ImageSize size ) const + QUrl imageUrl( ImageSize size ) const { return m_imageUrls.value(size); } /** * Whether the event is cancelled * @return true if the event is cancelled */ bool isCancelled() const { return m_cancelled; } /** * A getter for the event's name * @return the event's name */ QString name() const; /** * The list of participating artists (excluding the headliner) * @return list of participating artists (excluding the headliner) */ QStringList participants() const { return m_participants; } /** * The List of Last.fm tags * @return list of Last.fm tags */ QStringList tags() const { return m_tags; } /** * A getter for the event's page * @return the URL to the event's page */ - KUrl url() const; + QUrl url() const; /** * Get the venue associated with this event * @return the venue */ LastFmVenuePtr venue() const { return m_venue; } /** * Set the number of attendance * @param number the number of attendance */ void setAttendance( int number ) { m_attendance = number; } /** * Set whether the event has been cancelled * @param isCancelled whether the event has been cancelled */ void setCancelled( bool isCancelled ) { m_cancelled = isCancelled; } /** * Sets the event's date * @param date the event's date */ void setDate( const KDateTime &date ); /** * Sets the event's description * @param text the event's description */ void setDescription( const QString &text ) { m_description = text; } /** * Sets the headlining artist for this event * @param headliner the headlining artist for this event */ void setHeadliner( const QString &headliner ) { m_headliner = headliner; } /** * Sets the \p url for the event's image at \p size * @param size size of the image * @param url url of the image */ - void setImageUrl( ImageSize size, const KUrl &url ) + void setImageUrl( ImageSize size, const QUrl &url ) { m_imageUrls[size] = url; } /** * Sets the event's name * @param name the event's name */ void setName( const QString &name ); /** * Sets the participating artists (excluding headliner) at this event * @param participants artists participating at this event */ void setParticipants( const QStringList &participants ) { m_participants = participants; } /** * Sets the tags for this event * @param tags the tags for this event */ void setTags( const QStringList &tags ) { m_tags = tags; } /** * Sets the event's page * @param url the URL to the event's page */ - void setUrl( const KUrl &url ); + void setUrl( const QUrl &url ); /** * Sets the venue of this event * @param venue the venue of this event */ void setVenue( LastFmVenuePtr venue ) { m_venue = venue; } /** * Convert an ImageSize to a QString */ static QString imageSizeToString( ImageSize size ); /** * Convert a QString to an ImageSize */ static ImageSize stringToImageSize( const QString &string ); private: int m_attendance; //!< Number of the event's attendance bool m_cancelled; //!< Whether the event has been cancelled KDateTime m_date; //!< The event's start date - KUrl m_url; //!< The URL to the event's page + QUrl m_url; //!< The URL to the event's page ImageUrls m_imageUrls; //!< URLs to the event's image QString m_description; //!< Description of the event QString m_name; //!< The event's name QString m_headliner; //!< The headline artist of this event QStringList m_participants; //!< Other artists participating in the event QStringList m_tags; //!< Contextual tags LastFmVenuePtr m_venue; //!< Venue info }; class LastFmLocation : public QSharedData { public: LastFmLocation(); ~LastFmLocation(); LastFmLocation( const LastFmLocation &cpy ); QString city; QString country; QString street; QString postalCode; double latitude; double longitude; }; class LastFmVenue : public QSharedData { public: LastFmVenue(); ~LastFmVenue(); LastFmVenue( const LastFmVenue &cpy ); int id; QString name; - KUrl url; - KUrl website; + QUrl url; + QUrl website; QString phoneNumber; - QHash<LastFmEvent::ImageSize, KUrl> imageUrls; + QHash<LastFmEvent::ImageSize, QUrl> imageUrls; LastFmLocationPtr location; }; Q_DECLARE_METATYPE(LastFmEvent) Q_DECLARE_METATYPE(LastFmEventPtr) Q_DECLARE_METATYPE(LastFmEvent::List) Q_DECLARE_METATYPE(LastFmLocation) Q_DECLARE_METATYPE(LastFmLocationPtr) Q_DECLARE_METATYPE(LastFmVenue) Q_DECLARE_METATYPE(LastFmVenuePtr) #endif // LASTFMEVENT_H diff --git a/src/context/applets/upcomingevents/LastFmEventXmlParser.cpp b/src/context/applets/upcomingevents/LastFmEventXmlParser.cpp index 6535bb8583..abb054c007 100644 --- a/src/context/applets/upcomingevents/LastFmEventXmlParser.cpp +++ b/src/context/applets/upcomingevents/LastFmEventXmlParser.cpp @@ -1,231 +1,231 @@ /**************************************************************************************** * Copyright (c) 2010 Rick W. Chen <stuffcorpse@archlinux.us> * * * * 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, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ #include "LastFmEventXmlParser.h" LastFmEventXmlParser::LastFmEventXmlParser( QXmlStreamReader &reader ) : m_xml( reader ) { } LastFmEventXmlParser::~LastFmEventXmlParser() {} bool LastFmEventXmlParser::read() { while( !m_xml.atEnd() && !m_xml.hasError() ) { m_xml.readNext(); if( m_xml.isStartElement() && m_xml.name() == "event" ) { QHash<QString, QString> artists; LastFmEventPtr event( new LastFmEvent ); while( !m_xml.atEnd() ) { m_xml.readNext(); const QStringRef &n = m_xml.name(); if( m_xml.isEndElement() && n == "event" ) break; if( m_xml.isStartElement() ) { const QXmlStreamAttributes &a = m_xml.attributes(); if( n == "title" ) event->setName( m_xml.readElementText() ); else if( n == "artists" ) artists = readEventArtists(); else if( n == "venue" ) { LastFmVenueXmlParser venueParser( m_xml ); if( venueParser.read() ) event->setVenue( venueParser.venue() ); } else if( n == "startDate" ) event->setDate( KDateTime::fromString( m_xml.readElementText(), "%a, %d %b %Y %H:%M:%S" ) ); else if( n == "image" && a.hasAttribute("size") ) - event->setImageUrl( LastFmEvent::stringToImageSize(a.value("size").toString()), KUrl( m_xml.readElementText() ) ); + event->setImageUrl( LastFmEvent::stringToImageSize(a.value("size").toString()), QUrl( m_xml.readElementText() ) ); else if( n == "url" ) - event->setUrl( KUrl( m_xml.readElementText() ) ); + event->setUrl( QUrl( m_xml.readElementText() ) ); else if( n == "attendance" ) event->setAttendance( m_xml.readElementText().toInt() ); else if( n == "cancelled" ) event->setCancelled( bool( m_xml.readElementText().toInt() ) ); else if( n == "tags" ) event->setTags( readEventTags() ); else m_xml.skipCurrentElement(); } } event->setHeadliner( artists.value("headliner") ); event->setParticipants( artists.values("artist") ); m_events << event; } } return !m_xml.error(); } QStringList LastFmEventXmlParser::readEventTags() { QStringList tags; while( !m_xml.atEnd() ) { m_xml.readNext(); if( m_xml.isEndElement() && m_xml.name() == "tags" ) break; if( m_xml.isStartElement() ) { if( m_xml.name() == "tag" ) tags << m_xml.readElementText(); else m_xml.skipCurrentElement(); } } return tags; } QHash<QString, QString> LastFmEventXmlParser::readEventArtists() { QHash<QString, QString> artists; while( !m_xml.atEnd() ) { m_xml.readNext(); if( m_xml.isEndElement() && m_xml.name() == "artists" ) break; if( m_xml.isStartElement() ) { if( m_xml.name() == "artist" ) artists.insertMulti( "artist", m_xml.readElementText() ); else if( m_xml.name() == "headliner" ) artists.insert( "headliner", m_xml.readElementText() ); else m_xml.skipCurrentElement(); } } return artists; } LastFmVenueXmlParser::LastFmVenueXmlParser( QXmlStreamReader &reader ) : m_xml( reader ) {} bool LastFmVenueXmlParser::read() { m_venue = new LastFmVenue; while( !m_xml.atEnd() && !m_xml.hasError() ) { m_xml.readNext(); const QStringRef &n = m_xml.name(); if( m_xml.isEndElement() && n == "venue" ) break; if( m_xml.isStartElement() ) { const QXmlStreamAttributes &a = m_xml.attributes(); if( n == "id" ) m_venue->id = m_xml.readElementText().toInt(); else if( n == "name" ) m_venue->name = m_xml.readElementText(); else if( n == "location" ) { LastFmLocationXmlParser locationParser( m_xml ); if( locationParser.read() ) m_venue->location = locationParser.location(); } else if( n == "url" ) - m_venue->url = KUrl( m_xml.readElementText() ); + m_venue->url = QUrl( m_xml.readElementText() ); else if( n == "website" ) - m_venue->website = KUrl( m_xml.readElementText() ); + m_venue->website = QUrl( m_xml.readElementText() ); else if( n == "phonenumber" ) m_venue->phoneNumber = m_xml.readElementText(); else if( n == "image" && a.hasAttribute("size") ) { LastFmEvent::ImageSize size = LastFmEvent::stringToImageSize( a.value("size").toString() ); - m_venue->imageUrls[ size ] = KUrl( m_xml.readElementText() ); + m_venue->imageUrls[ size ] = QUrl( m_xml.readElementText() ); } else m_xml.skipCurrentElement(); } } return !m_xml.error(); } LastFmVenueXmlParser::~LastFmVenueXmlParser() {} LastFmLocationXmlParser::LastFmLocationXmlParser( QXmlStreamReader &reader ) : m_xml( reader ) {} bool LastFmLocationXmlParser::read() { m_location = new LastFmLocation; while( !m_xml.atEnd() && !m_xml.hasError() ) { m_xml.readNext(); if( m_xml.isEndElement() && m_xml.name() == "location" ) break; if( m_xml.isStartElement() ) { if( m_xml.name() == "city" ) m_location->city = m_xml.readElementText(); else if( m_xml.name() == "country" ) m_location->country = m_xml.readElementText(); else if( m_xml.name() == "street" ) m_location->street = m_xml.readElementText(); else if( m_xml.name() == "postalcode" ) m_location->postalCode = m_xml.readElementText(); else if( m_xml.prefix() == "geo" && m_xml.name() == "point" ) readGeoPoint(); else m_xml.skipCurrentElement(); } } return !m_xml.error(); } LastFmLocationXmlParser::~LastFmLocationXmlParser() {} void LastFmLocationXmlParser::readGeoPoint() { while( !m_xml.atEnd() && !m_xml.hasError() ) { m_xml.readNext(); if( m_xml.isEndElement() && m_xml.name() == "point" ) break; if( m_xml.isStartElement() ) { if( m_xml.name() == "lat" ) m_location->latitude = m_xml.readElementText().toDouble(); else if( m_xml.name() == "long" ) m_location->longitude = m_xml.readElementText().toDouble(); else m_xml.skipCurrentElement(); } } } diff --git a/src/context/applets/upcomingevents/UpcomingEventsApplet.cpp b/src/context/applets/upcomingevents/UpcomingEventsApplet.cpp index b4450929c2..3132444b2f 100644 --- a/src/context/applets/upcomingevents/UpcomingEventsApplet.cpp +++ b/src/context/applets/upcomingevents/UpcomingEventsApplet.cpp @@ -1,656 +1,656 @@ /**************************************************************************************** * Copyright (c) 2009 Joffrey Clavel <jclavel@clabert.info> * * Copyright (c) 2009 Oleksandr Khayrullin <saniokh@gmail.com> * * Copyright (c) 2009-2010 Ludovic Deveaux <deveaux.ludovic31@gmail.com> * * Copyright (c) 2010 Hormiere Guillaume <hormiere.guillaume@gmail.com> * * * * 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, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ #define DEBUG_PREFIX "UpcomingEventsApplet" #include "UpcomingEventsApplet.h" #include "amarokurls/AmarokUrl.h" #include "context/applets/upcomingevents/LastFmEvent.h" #include "context/widgets/AppletHeader.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include "SvgHandler.h" #include "LastFmEventXmlParser.h" #include "UpcomingEventsMapWidget.h" #include "UpcomingEventsCalendarWidget.h" #include "UpcomingEventsStack.h" #include "UpcomingEventsStackItem.h" #include <KConfigDialog> #include <KGlobalSettings> #include <Plasma/IconWidget> #include <Plasma/Theme> #include <Plasma/Svg> #include <QDesktopServices> #include <QGraphicsLinearLayout> #include <QXmlStreamReader> UpcomingEventsApplet::UpcomingEventsApplet( QObject* parent, const QVariantList& args ) : Context::Applet( parent, args ) , m_groupVenues( false ) , m_stack( 0 ) { setHasConfigurationInterface( true ); setBackgroundHints( Plasma::Applet::NoBackground ); } UpcomingEventsApplet::~UpcomingEventsApplet() { } void UpcomingEventsApplet::init() { DEBUG_BLOCK Context::Applet::init(); enableHeader( true ); setHeaderText( i18n( "Upcoming Events" ) ); m_stack = new UpcomingEventsStack( this ); m_stack->setContentsMargins( 0, 0, 0, 0 ); connect( m_stack, SIGNAL(collapseStateChanged()), SLOT(collapseStateChanged()) ); connect( this, SIGNAL(listWidgetRemoved(UpcomingEventsListWidget*)), m_stack, SLOT(cleanupListWidgets()) ); QAction *calendarAction = new QAction( this ); calendarAction->setIcon( KIcon( "view-calendar" ) ); calendarAction->setToolTip( i18n( "View Events Calendar" ) ); Plasma::IconWidget *calendarIcon = addLeftHeaderAction( calendarAction ); connect( calendarIcon, SIGNAL(clicked()), this, SLOT(viewCalendar()) ); QAction* settingsAction = new QAction( this ); settingsAction->setIcon( KIcon( "preferences-system" ) ); settingsAction->setToolTip( i18n( "Settings" ) ); settingsAction->setEnabled( true ); Plasma::IconWidget *settingsIcon = addRightHeaderAction( settingsAction ); connect( settingsIcon, SIGNAL(clicked()), this, SLOT(configure()) ); m_artistStackItem = m_stack->create( QLatin1String("currentartistevents") ); m_artistEventsList = new UpcomingEventsListWidget( m_artistStackItem ); m_artistStackItem->setTitle( i18nc( "@title:group", "No track is currently playing" ) ); m_artistStackItem->setWidget( m_artistEventsList ); m_artistStackItem->setCollapsed( true ); m_artistStackItem->setIcon( KIcon("filename-artist-amarok") ); connect( m_artistEventsList, SIGNAL(mapRequested(QObject*)), SLOT(handleMapRequest(QObject*)) ); QGraphicsLinearLayout *layout = new QGraphicsLinearLayout( Qt::Vertical ); layout->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ); layout->addItem( m_header ); layout->addItem( m_stack ); setLayout( layout ); // Read config and inform the engine. enableVenueGrouping( Amarok::config("UpcomingEvents Applet").readEntry( "groupVenues", false ) ); QStringList venueData = Amarok::config("UpcomingEvents Applet").readEntry( "favVenues", QStringList() ); m_favoriteVenues = venueStringToDataList( venueData ); Plasma::DataEngine *engine = dataEngine( "amarok-upcomingEvents" ); connect( engine, SIGNAL(sourceAdded(QString)), SLOT(engineSourceAdded(QString)) ); engine->query( "artistevents" ); engine->query( "venueevents" ); updateConstraints(); } void UpcomingEventsApplet::engineSourceAdded( const QString &source ) { if( source == "artistevents" || source == "venueevents" ) dataEngine( "amarok-upcomingEvents" )->connectSource( source, this ); } void UpcomingEventsApplet::constraintsEvent( Plasma::Constraints constraints ) { Context::Applet::constraintsEvent( constraints ); prepareGeometryChange(); setHeaderText( i18n( "Upcoming Events" ) ); update(); } void UpcomingEventsApplet::dataUpdated( const QString &source, const Plasma::DataEngine::Data &data ) { const LastFmEvent::List &events = data[ "events" ].value< LastFmEvent::List >(); if( source == "artistevents" ) { QString artistName = data[ "artist" ].toString(); m_artistEventsList->clear(); m_artistEventsList->setName( artistName ); addToStackItem( m_artistStackItem, events, artistName ); if( !m_artistStackItem->action( "showinmediasources" ) ) { QAction *act = new QAction( KIcon("edit-find"), QString(), m_artistStackItem ); act->setToolTip( i18n( "Show in Media Sources" ) ); connect( act, SIGNAL(triggered()), this, SLOT(navigateToArtist()) ); m_artistStackItem->addAction( "showinmediasources", act ); } m_artistStackItem->setCollapsed( events.isEmpty() ); } else if( source == "venueevents" ) { if( !events.isEmpty() ) { LastFmVenuePtr venue = data[ "venue" ].value<LastFmVenuePtr>(); if( m_groupVenues && m_stack->hasItem("favoritevenuesgroup") ) { QString title = i18n( "Favorite Venues" ); addToStackItem( m_stack->item("favoritevenuesgroup"), events, title ); } else { UpcomingEventsStackItem *stackItem( 0 ); UpcomingEventsListWidget *listWidget( 0 ); LastFmEvent::List newEvents; if( !m_stack->hasItem( venue->name ) ) { stackItem = m_stack->create( venue->name ); listWidget = new UpcomingEventsListWidget( stackItem ); listWidget->setName( venue->name ); stackItem->setWidget( listWidget ); stackItem->setCollapsed( true ); stackItem->setIcon( KIcon("favorites") ); stackItem->showCloseButton(); connect( listWidget, SIGNAL(mapRequested(QObject*)), SLOT(handleMapRequest(QObject*)) ); connect( listWidget, SIGNAL(destroyed(QObject*)), SLOT(listWidgetDestroyed(QObject*)) ); emit listWidgetAdded( listWidget ); newEvents = events; } else { stackItem = m_stack->item( venue->name ); typedef UpcomingEventsListWidget UELW; UELW *widget = static_cast<UELW*>( stackItem->widget() ); newEvents = events.toSet().subtract( widget->events().toSet() ).toList(); } addToStackItem( stackItem, newEvents, venue->name ); } update(); } else if( m_groupVenues && m_stack->hasItem( QLatin1String("favoritevenuesgroup") ) ) { m_stack->remove( QLatin1String("favoritevenuesgroup" ) ); } else { // remove all venue lists const QRegExp pattern( QLatin1String("^(?!(currentartistevents|venuemapview|calendar)).*$") ); QList<UpcomingEventsStackItem*> eventItems = m_stack->items( pattern ); qDeleteAll( eventItems ); } } } void UpcomingEventsApplet::clearVenueItems() { m_stack->remove( QLatin1String("favoritevenuesgroup" ) ); m_stack->remove( QLatin1String("venuemapview" ) ); } void UpcomingEventsApplet::addToStackItem( UpcomingEventsStackItem *item, const LastFmEvent::List &events, const QString &name ) { UpcomingEventsListWidget *listWidget = static_cast<UpcomingEventsListWidget*>( item->widget() ); listWidget->addEvents( events ); QString title; int added = listWidget->count(); if( added == 0 ) { title = name.isEmpty() ? i18n( "No upcoming events" ) : i18n( "%1: No upcoming events", name ); } else { title = name.isEmpty() ? i18ncp( "@title:group Number of upcoming events", "1 event", "%1 events", added ) : i18ncp( "@title:group Number of upcoming events", "%1: 1 event", "%1: %2 events", name, added ); } item->setTitle( title ); item->layout()->invalidate(); } void UpcomingEventsApplet::paintInterface( QPainter *p, const QStyleOptionGraphicsItem *option, const QRect &contentsRect ) { Q_UNUSED( option ) Q_UNUSED( contentsRect ) addGradientToAppletBackground( p ); } void UpcomingEventsApplet::configure() { DEBUG_BLOCK showConfigurationInterface(); } void UpcomingEventsApplet::createConfigurationInterface( KConfigDialog *parent ) { QWidget *generalSettings = new QWidget; QWidget *venueSettings = new QWidget; ui_GeneralSettings.setupUi( generalSettings ); ui_VenueSettings.setupUi( venueSettings ); // TODO bad, it's done manually ... QString timeSpan = Amarok::config("UpcomingEvents Applet").readEntry( "timeSpan", "AllEvents" ); if( timeSpan == "AllEvents" ) ui_GeneralSettings.filterComboBox->setCurrentIndex( 0 ); else if( timeSpan == "ThisWeek" ) ui_GeneralSettings.filterComboBox->setCurrentIndex( 1 ); else if( timeSpan == "ThisMonth" ) ui_GeneralSettings.filterComboBox->setCurrentIndex( 2 ); else if( timeSpan == "ThisYear" ) ui_GeneralSettings.filterComboBox->setCurrentIndex( 3 ); connect( ui_VenueSettings.searchLineEdit, SIGNAL(returnPressed(QString)), SLOT(searchVenue(QString)) ); connect( ui_VenueSettings.searchResultsList, SIGNAL(itemClicked(QListWidgetItem*)), SLOT(showVenueInfo(QListWidgetItem*)) ); connect( ui_VenueSettings.selectedVenuesList, SIGNAL(itemClicked(QListWidgetItem*)), SLOT(showVenueInfo(QListWidgetItem*)) ); connect( ui_VenueSettings.searchResultsList, SIGNAL(itemDoubleClicked(QListWidgetItem*)), SLOT(venueResultDoubleClicked(QListWidgetItem*)) ); connect( ui_VenueSettings.selectedVenuesList, SIGNAL(itemDoubleClicked(QListWidgetItem*)), SLOT(selectedVenueDoubleClicked(QListWidgetItem*)) ); connect( ui_VenueSettings.urlValue, SIGNAL(leftClickedUrl(QString)), SLOT(openUrl(QString)) ); connect( ui_VenueSettings.urlValue, SIGNAL(rightClickedUrl(QString)), SLOT(openUrl(QString)) ); connect( ui_VenueSettings.websiteValue, SIGNAL(leftClickedUrl(QString)), SLOT(openUrl(QString)) ); connect( ui_VenueSettings.websiteValue, SIGNAL(rightClickedUrl(QString)), SLOT(openUrl(QString)) ); connect( parent, SIGNAL(okClicked()), SLOT(saveSettings()) ); ui_VenueSettings.photoLabel->hide(); ui_VenueSettings.infoGroupBox->setFont( KGlobalSettings::smallestReadableFont() ); ui_GeneralSettings.groupVenueCheckBox->setCheckState( m_groupVenues ? Qt::Checked : Qt::Unchecked ); ui_VenueSettings.countryCombo->insertSeparator( 1 ); const QStringList &countryCodes = KGlobal::locale()->allCountriesList(); foreach( const QString &code, countryCodes ) ui_VenueSettings.countryCombo->addItem( KGlobal::locale()->countryCodeToName(code), code ); foreach( const VenueData &data, m_favoriteVenues ) { QListWidgetItem *item = new QListWidgetItem; item->setData( VenueIdRole, data.id ); item->setData( VenueCityRole, data.city ); item->setData( VenueNameRole, data.name ); item->setText( QString( "%1, %2" ) .arg( item->data( VenueNameRole ).toString() ) .arg( item->data( VenueCityRole ).toString() ) ); ui_VenueSettings.selectedVenuesList->addItem( item ); } parent->addPage( generalSettings, i18n( "Upcoming Events Settings" ), "preferences-system"); parent->addPage( venueSettings, i18n( "Favorite Venues" ), "favorites" ); } void UpcomingEventsApplet::venueResultDoubleClicked( QListWidgetItem *item ) { if( !item ) return; int row = ui_VenueSettings.searchResultsList->row( item ); QListWidgetItem *moveItem = ui_VenueSettings.searchResultsList->takeItem( row ); ui_VenueSettings.searchResultsList->clearSelection(); ui_VenueSettings.selectedVenuesList->addItem( moveItem ); ui_VenueSettings.selectedVenuesList->setCurrentItem( moveItem ); } void UpcomingEventsApplet::selectedVenueDoubleClicked( QListWidgetItem *item ) { if( !item ) return; int row = ui_VenueSettings.selectedVenuesList->row( item ); QListWidgetItem *moveItem = ui_VenueSettings.selectedVenuesList->takeItem( row ); ui_VenueSettings.selectedVenuesList->clearSelection(); ui_VenueSettings.searchResultsList->addItem( moveItem ); ui_VenueSettings.searchResultsList->setCurrentItem( moveItem ); } void UpcomingEventsApplet::showVenueInfo( QListWidgetItem *item ) { if( !item ) return; const QString &name = item->data( VenueNameRole ).toString(); const QString &city = item->data( VenueCityRole ).toString(); const QString &country = item->data( VenueCountryRole ).toString(); const QString &street = item->data( VenueStreetRole ).toString(); - const KUrl &url = item->data( VenueUrlRole ).value<KUrl>(); - const KUrl &website = item->data( VenueWebsiteRole ).value<KUrl>(); - const KUrl &photoUrl = item->data( VenuePhotoUrlRole ).value<KUrl>(); + const QUrl &url = item->data( VenueUrlRole ).value<QUrl>(); + const QUrl &website = item->data( VenueWebsiteRole ).value<QUrl>(); + const QUrl &photoUrl = item->data( VenuePhotoUrlRole ).value<QUrl>(); ui_VenueSettings.nameValue->setText( name ); ui_VenueSettings.cityValue->setText( city ); ui_VenueSettings.countryValue->setText( country ); ui_VenueSettings.streetValue->setText( street ); if( url.isValid() ) { ui_VenueSettings.urlValue->setText( i18nc("@label:textbox Url label", "link") ); ui_VenueSettings.urlValue->setTipText( url.url() ); ui_VenueSettings.urlValue->setUrl( url.url() ); } else ui_VenueSettings.urlValue->clear(); if( website.isValid() ) { ui_VenueSettings.websiteValue->setText( i18nc("@label:textbox Url label", "link") ); ui_VenueSettings.websiteValue->setTipText( website.url() ); ui_VenueSettings.websiteValue->setUrl( website.url() ); } else ui_VenueSettings.websiteValue->clear(); if( photoUrl.isValid() ) { The::networkAccessManager()->getData( photoUrl, this, - SLOT(venuePhotoResult(KUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); + SLOT(venuePhotoResult(QUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); } else { ui_VenueSettings.photoLabel->hide(); ui_VenueSettings.photoLabel->clear(); } } void UpcomingEventsApplet::searchVenue( const QString &text ) { - KUrl url; + QUrl url; url.setScheme( "http" ); url.setHost( "ws.audioscrobbler.com" ); url.setPath( "/2.0/" ); url.addQueryItem( "method", "venue.search" ); url.addQueryItem( "api_key", Amarok::lastfmApiKey() ); url.addQueryItem( "venue", text ); int currentCountryIndex = ui_VenueSettings.countryCombo->currentIndex(); const QString &countryCode = ui_VenueSettings.countryCombo->itemData( currentCountryIndex ).toString(); if( !countryCode.isEmpty() ) url.addQueryItem( "country", countryCode ); The::networkAccessManager()->getData( url, this, - SLOT(venueResults(KUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); + SLOT(venueResults(QUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); } void -UpcomingEventsApplet::venueResults( const KUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ) +UpcomingEventsApplet::venueResults( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ) { Q_UNUSED( url ) if( e.code != QNetworkReply::NoError ) { debug() << "Failed to get venue results:" << e.description; return; } ui_VenueSettings.searchResultsList->clear(); QXmlStreamReader xml( data ); while( !xml.atEnd() ) { xml.readNext(); if( xml.isStartElement() && xml.name() == "venue" ) { LastFmVenueXmlParser venueParser( xml ); if( venueParser.read() ) { QListWidgetItem *item = new QListWidgetItem; LastFmVenuePtr venue = venueParser.venue(); item->setData( VenueIdRole, venue->id ); item->setData( VenueNameRole, venue->name ); item->setData( VenuePhotoUrlRole, venue->imageUrls[LastFmEvent::Large] ); item->setData( VenueUrlRole, venue->url ); item->setData( VenueWebsiteRole, venue->website ); LastFmLocationPtr location = venue->location; item->setData( VenueCityRole, location->city ); item->setData( VenueCountryRole, location->country ); item->setData( VenueStreetRole, location->street ); item->setText( QString( "%1, %2" ) .arg( item->data( VenueNameRole ).toString() ) .arg( item->data( VenueCityRole ).toString() ) ); ui_VenueSettings.searchResultsList->addItem( item ); } } } } void -UpcomingEventsApplet::venuePhotoResult( const KUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ) +UpcomingEventsApplet::venuePhotoResult( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ) { Q_UNUSED( url ) if( e.code != QNetworkReply::NoError ) { debug() << "Failed to get venue photo:" << e.description; return; } QPixmap photo; if( photo.loadFromData( data ) ) { photo = photo.scaled( 140, 140, Qt::KeepAspectRatio, Qt::SmoothTransformation ); photo = The::svgHandler()->addBordersToPixmap( photo, 5, QString(), true ); ui_VenueSettings.photoLabel->setPixmap( photo ); ui_VenueSettings.photoLabel->show(); } } QList<UpcomingEventsApplet::VenueData> UpcomingEventsApplet::venueStringToDataList( const QStringList &list ) { // config qstringlist is stored as format: QString(id;name;city), QString(id;name;city), ... QList<VenueData> dataList; foreach( const QString &item, list ) { const QStringList &frag = item.split( QChar(';') ); VenueData data = { frag.at( 0 ).toInt(), frag.at( 1 ), frag.at( 2 ) }; dataList << data; } return dataList; } void UpcomingEventsApplet::openUrl( const QString &url ) { QDesktopServices::openUrl( QUrl(url) ); } UpcomingEventsMapWidget * UpcomingEventsApplet::mapView() { if( m_stack->hasItem("venuemapview") ) { UpcomingEventsStackItem *item = m_stack->item( "venuemapview" ); return static_cast<UpcomingEventsMapWidget*>( item->widget() ); } UpcomingEventsStackItem *stackItem = m_stack->create( QLatin1String("venuemapview") ); UpcomingEventsMapWidget *view = new UpcomingEventsMapWidget( stackItem ); stackItem->setIcon( KIcon( "edit-find" ) ); stackItem->setTitle( i18n( "Map View" ) ); stackItem->setWidget( view ); stackItem->setMinimumWidth( 50 ); stackItem->showCloseButton(); m_stack->setMinimumWidth( 50 ); const QRegExp pattern( QLatin1String("^(?!(venuemapview|calendar)).*$") ); QList<UpcomingEventsStackItem*> eventItems = m_stack->items( pattern ); foreach( UpcomingEventsStackItem *item, eventItems ) { typedef UpcomingEventsListWidget LW; if( item ) view->addEventsListWidget( qgraphicsitem_cast<LW*>( item->widget() ) ); } connect( this, SIGNAL(listWidgetAdded(UpcomingEventsListWidget*)), view, SLOT(addEventsListWidget(UpcomingEventsListWidget*)) ); connect( this, SIGNAL(listWidgetRemoved(UpcomingEventsListWidget*)), view, SLOT(removeEventsListWidget(UpcomingEventsListWidget*)) ); return view; } void UpcomingEventsApplet::collapseStateChanged() { emit sizeHintChanged( Qt::PreferredSize ); } void UpcomingEventsApplet::viewCalendar() { if( m_stack->hasItem("calendar") ) { m_stack->item("calendar")->setCollapsed( false ); return; } UpcomingEventsStackItem *stackItem = m_stack->create( QLatin1String("calendar") ); UpcomingEventsCalendarWidget *calendar = new UpcomingEventsCalendarWidget( stackItem ); stackItem->setIcon( KIcon( "view-calendar" ) ); stackItem->setTitle( i18n( "Events Calendar" ) ); stackItem->setWidget( calendar ); stackItem->setMinimumWidth( 50 ); stackItem->showCloseButton(); stackItem->addAction( "jumptotoday", calendar->todayAction() ); const QRegExp pattern( QLatin1String("^(?!(venuemapview|calendar)).*$") ); QList<UpcomingEventsStackItem*> eventItems = m_stack->items( pattern ); foreach( UpcomingEventsStackItem *item, eventItems ) { typedef UpcomingEventsListWidget LW; if( item ) calendar->addEvents( qgraphicsitem_cast<LW*>( item->widget() )->events() ); } } QString UpcomingEventsApplet::currentTimeSpan() { QString span = ui_GeneralSettings.filterComboBox->currentText(); if( span == i18n("This week") ) return "ThisWeek"; else if( span == i18n("This month") ) return "ThisMonth"; else if( span == i18n("This year") ) return "ThisYear"; else return "AllEvents"; } void UpcomingEventsApplet::navigateToArtist() { if( m_artistEventsList->name().isEmpty() ) return; AmarokUrl url; url.setCommand( "navigate" ); url.setPath( "collections" ); url.setArg( "filter", "artist:\"" + m_artistEventsList->name() + "\"" ); url.run(); } void UpcomingEventsApplet::handleMapRequest( QObject *widget ) { if( !mapView()->isLoaded() ) { UpcomingEventsWidget *eventWidget = static_cast<UpcomingEventsWidget*>( widget ); LastFmVenuePtr venue = eventWidget->eventPtr()->venue(); mapView()->centerAt( venue ); m_stack->maximizeItem( QLatin1String("venuemapview") ); } } void UpcomingEventsApplet::listWidgetDestroyed( QObject *obj ) { UpcomingEventsListWidget *widget = static_cast<UpcomingEventsListWidget*>( obj ); emit listWidgetRemoved( widget ); } void UpcomingEventsApplet::saveTimeSpan() { DEBUG_BLOCK Amarok::config("UpcomingEvents Applet").writeEntry( "timeSpan", currentTimeSpan() ); dataEngine( "amarok-upcomingEvents" )->query( QString( "timespan:update" ) ); } void UpcomingEventsApplet::saveSettings() { clearVenueItems(); saveTimeSpan(); // save venue settings QStringList venueConfig; m_favoriteVenues.clear(); for( int i = 0, count = ui_VenueSettings.selectedVenuesList->count() ; i < count; ++i ) { int itemId = ui_VenueSettings.selectedVenuesList->item( i )->data( VenueIdRole ).toString().toInt(); QString itemCity = ui_VenueSettings.selectedVenuesList->item( i )->data( VenueCityRole ).toString(); QString itemName = ui_VenueSettings.selectedVenuesList->item( i )->data( VenueNameRole ).toString(); VenueData data = { itemId, itemName, itemCity }; m_favoriteVenues << data; venueConfig << (QStringList() << QString::number(itemId) << itemName << itemCity).join( QChar(';') ); } Amarok::config("UpcomingEvents Applet").writeEntry( "favVenues", venueConfig ); enableVenueGrouping( ui_GeneralSettings.groupVenueCheckBox->checkState() == Qt::Checked ); Amarok::config("UpcomingEvents Applet").writeEntry( "groupVenues", m_groupVenues ); if( !m_favoriteVenues.isEmpty() ) dataEngine( "amarok-upcomingEvents" )->query( "venueevents:update" ); } void UpcomingEventsApplet::enableVenueGrouping( bool enable ) { m_groupVenues = enable; if( enable ) { if( !m_stack->hasItem("favoritevenuesgroup") ) { UpcomingEventsStackItem *item = m_stack->create( QLatin1String("favoritevenuesgroup") ); UpcomingEventsListWidget *listWidget = new UpcomingEventsListWidget( item ); listWidget->setName( i18nc( "@title:group", "Favorite Venues" ) ); QString title = i18ncp("@title:group Number of upcoming events", "%1: 1 event", "%1: %2 events", listWidget->name(), listWidget->count()); item->setTitle( title ); item->setIcon( "favorites" ); item->setWidget( listWidget ); connect( listWidget, SIGNAL(mapRequested(QObject*)), SLOT(handleMapRequest(QObject*)) ); connect( listWidget, SIGNAL(destroyed(QObject*)), SLOT(listWidgetDestroyed(QObject*)) ); emit listWidgetAdded( listWidget ); } } else { m_stack->remove( QLatin1String("favoritevenuesgroup" ) ); } updateConstraints(); } diff --git a/src/context/applets/upcomingevents/UpcomingEventsApplet.h b/src/context/applets/upcomingevents/UpcomingEventsApplet.h index 8f9bb941a8..335b73621b 100644 --- a/src/context/applets/upcomingevents/UpcomingEventsApplet.h +++ b/src/context/applets/upcomingevents/UpcomingEventsApplet.h @@ -1,224 +1,224 @@ /**************************************************************************************** * Copyright (c) 2009 Joffrey Clavel <jclavel@clabert.info> * * Copyright (c) 2009 Oleksandr Khayrullin <saniokh@gmail.com> * * Copyright (c) 2009-2010 Ludovic Deveaux <deveaux.ludovic31@gmail.com> * * * * 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, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ #ifndef UPCOMING_EVENTS_APPLET_H #define UPCOMING_EVENTS_APPLET_H // Includes #include "context/Applet.h" #include "context/DataEngine.h" #include "network/NetworkAccessManagerProxy.h" #include "UpcomingEventsWidget.h" #include "ui_upcomingEventsGeneralSettings.h" #include "ui_upcomingEventsVenueSettings.h" class KConfigDialog; class QGraphicsLinearLayout; class QListWidgetItem; class QXmlStreamReader; class UpcomingEventsMapWidget; class UpcomingEventsStackItem; class UpcomingEventsStack; namespace Plasma { class WebView; } /** * \class UpcomingEventsApplet UpcomingEventsApplet.h UpcomingEventsApplet.cpp * \brief The base class of the Upcoming Events applet. * * UpcomingEventsApplet displays the upcoming events of the current artist from LastFm. */ class UpcomingEventsApplet : public Context::Applet { Q_OBJECT public: /** * \brief Constructor * * UpcomingEventsApplet constructor * * \param parent : the UpcomingEventsApplet parent (used by Context::Applet) * \param args : (used by Context::Applet) */ UpcomingEventsApplet( QObject* parent, const QVariantList& args ); virtual ~UpcomingEventsApplet(); /** * \brief Paints the interface * * This method is called when the interface should be painted * * \param painter : the QPainter to use to do the paintiner * \param option : the style options object * \param contentsRect : the rect to paint within; automatically adjusted for * the background, if any */ void paintInterface( QPainter *painter, const QStyleOptionGraphicsItem* option, const QRect& contentsRect ); /** * Called when any of the geometry constraints have been updated. * * This is always called prior to painting and should be used as an * opportunity to layout the widget, calculate sizings, etc. * * Do not call update() from this method; an update() will be triggered * at the appropriate time for the applet. * * \param constraints : the type of constraints that were updated */ void constraintsEvent( Plasma::Constraints constraints = Plasma::AllConstraints ); signals: void listWidgetAdded( UpcomingEventsListWidget *widget ); void listWidgetRemoved( UpcomingEventsListWidget *widget ); protected: /** * Reimplement this method so provide a configuration interface, * parented to the supplied widget. Ownership of the widgets is passed * to the parent widget. * * \param parent : the dialog which is the parent of the configuration * widgets */ void createConfigurationInterface(KConfigDialog *parent); public slots: /** * \brief Initialization * * Initializes the UpcomingEventsApplet with default parameters */ virtual void init(); /** * Updates the data from the Upcoming Events engine * * \param name : the name * \param data : the engine from where the data are received */ void dataUpdated( const QString& name, const Plasma::DataEngine::Data& data ); private: /** * The UI of the general settings page */ Ui::upcomingEventsGeneralSettings ui_GeneralSettings; /** * The UI of the sticky venue settings page */ Ui::upcomingEventsVenueSettings ui_VenueSettings; /** * The stack item for the upcoming events for the current playing artist */ UpcomingEventsStackItem *m_artistStackItem; /** * The list widget presenting upcoming events */ UpcomingEventsListWidget *m_artistEventsList; private slots: /** * Connects the source to the Upcoming Events engine * and calls the dataUpdated function */ void engineSourceAdded( const QString &source ); /** * Show the settings windows */ void configure(); /** * Returns the current time span */ QString currentTimeSpan(); /** * Save the time span chosen by the user */ void saveTimeSpan(); /** * Save all the upcoming events settings */ void saveSettings(); /** * Show in media sources slot */ void navigateToArtist(); private: enum VenueItemRoles { VenueIdRole = Qt::UserRole, VenueNameRole, VenueCityRole, VenueCountryRole, VenueStreetRole, VenuePhotoUrlRole, VenueUrlRole, VenueWebsiteRole }; struct VenueData { int id; QString name; QString city; }; void clearVenueItems(); void addToStackItem( UpcomingEventsStackItem *item, const LastFmEvent::List &events, const QString &name ); QList<VenueData> venueStringToDataList( const QStringList &list ); QList<VenueData> m_favoriteVenues; void enableVenueGrouping( bool enable ); bool m_groupVenues; UpcomingEventsStack *m_stack; UpcomingEventsMapWidget *mapView(); private slots: void searchVenue( const QString &text ); - void venueResults( const KUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ); - void venuePhotoResult( const KUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ); + void venueResults( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ); + void venuePhotoResult( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ); void showVenueInfo( QListWidgetItem *item ); void venueResultDoubleClicked( QListWidgetItem *item ); void selectedVenueDoubleClicked( QListWidgetItem *item ); void handleMapRequest( QObject *widget ); void listWidgetDestroyed( QObject *obj ); void openUrl( const QString &url ); void collapseStateChanged(); void viewCalendar(); }; AMAROK_EXPORT_APPLET( upcomingEvents, UpcomingEventsApplet ) #endif // UPCOMINGEVENTSAPPLET_H diff --git a/src/context/applets/upcomingevents/UpcomingEventsMapWidget.cpp b/src/context/applets/upcomingevents/UpcomingEventsMapWidget.cpp index 306e731ce4..c2b3c5cc3b 100644 --- a/src/context/applets/upcomingevents/UpcomingEventsMapWidget.cpp +++ b/src/context/applets/upcomingevents/UpcomingEventsMapWidget.cpp @@ -1,355 +1,355 @@ /**************************************************************************************** * Copyright (c) 2010 Rick W. Chen <stuffcorpse@archlinux.us> * * * * 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, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ #include "UpcomingEventsMapWidget.h" #include "UpcomingEventsWidget.h" #include "network/NetworkAccessManagerProxy.h" #include <KLocale> #include <KStandardDirs> #include <QDesktopServices> #include <QFile> #include <QFileInfo> #include <QTimer> #include <QWebView> #include <QWebFrame> class UpcomingEventsMapWidgetPrivate { public: UpcomingEventsMapWidgetPrivate( UpcomingEventsMapWidget *parent ); ~UpcomingEventsMapWidgetPrivate(); void addEvent( const LastFmEventPtr &event ); void addMarker( const LastFmEventPtr &event ); QString createInfoString( const LastFmEventPtr &event ) const; - KUrl eventForMapIcon( const LastFmEventPtr &event ) const; + QUrl eventForMapIcon( const LastFmEventPtr &event ) const; void removeEvent( const LastFmEventPtr &event ); void removeMarker( const LastFmEventPtr &event ); void _init(); void _linkClicked( const QUrl &url ); void _loadFinished( bool success ); void _centerAt( QObject *obj ); LastFmEvent::List events; LastFmEvent::List eventQueue; QSet<UpcomingEventsListWidget*> listWidgets; QPointF centerWhenLoaded; bool isLoaded; private: UpcomingEventsMapWidget *const q_ptr; Q_DECLARE_PUBLIC( UpcomingEventsMapWidget ) }; UpcomingEventsMapWidgetPrivate::UpcomingEventsMapWidgetPrivate( UpcomingEventsMapWidget *parent ) : isLoaded( false ) , q_ptr( parent ) { } UpcomingEventsMapWidgetPrivate::~UpcomingEventsMapWidgetPrivate() { } void UpcomingEventsMapWidgetPrivate::addEvent( const LastFmEventPtr &event ) { if( !isLoaded ) { eventQueue << event; return; } events << event; addMarker( event ); } void UpcomingEventsMapWidgetPrivate::addMarker( const LastFmEventPtr &event ) { Q_Q( UpcomingEventsMapWidget ); LastFmLocationPtr loc = event->venue()->location; QString js = QString( "javascript:addMarker(%1,%2,'%3','%4')" ) .arg( QString::number( loc->latitude ) ) .arg( QString::number( loc->longitude ) ) .arg( eventForMapIcon(event).url() ) .arg( createInfoString(event) ); q->page()->mainFrame()->evaluateJavaScript( js ); } void UpcomingEventsMapWidgetPrivate::removeEvent( const LastFmEventPtr &event ) { eventQueue.removeAll( event ); if( isLoaded ) { events.removeAll( event ); removeMarker( event ); } } void UpcomingEventsMapWidgetPrivate::removeMarker( const LastFmEventPtr &event ) { Q_Q( UpcomingEventsMapWidget ); LastFmLocationPtr loc = event->venue()->location; QString js = QString( "javascript:removeMarker(%1,%2)" ) .arg( QString::number( loc->latitude ) ) .arg( QString::number( loc->longitude ) ); q->page()->mainFrame()->evaluateJavaScript( js ); } QString UpcomingEventsMapWidgetPrivate::createInfoString( const LastFmEventPtr &event ) const { QString name = event->name(); if( event->isCancelled() ) name = i18nc( "@label:textbox Title for a canceled upcoming event", "<s>%1</s> (Canceled)", name ); QStringList artists = event->artists(); artists.removeDuplicates(); QString desc = event->description(); KDateTime dt = event->date(); QStringList tags = event->tags(); LastFmVenuePtr venue = event->venue(); QString venueWebsite = venue->website.url(); QString venueLastFmUrl = venue->url.url(); QString location = venue->location->city; if( !venue->location->street.isEmpty() ) location.prepend( venue->location->street + ", " ); QString html = QString( "<div><img src=\"%1\" alt=\"\" style=\"float:right;margin:5px;clear:right\"/></div>" \ "<div><img src=\"%2\" alt=\"\" style=\"float:right;margin:5px;clear:right\"/></div>" \ "<div id=\"bodyContent\">" \ "<small>" \ "<b>Event:</b> %3<br/>" \ "<b>Artists:</b> %4<br/>" \ "<b>Time:</b> %5<br/>" \ "<b>Date:</b> %6<br/>" \ "<b>Venue:</b> %7<br/>" \ "<b>Location:</b> %8<br/>" \ "<b>Description:</b> %9<br/>" \ "<b>Tags:</b> %10<br/>" \ "<b>Event Website:</b> <a href=\"%11\">Last.fm</a><br/>" \ "<b>Venue Website:</b> <a href=\"%12\">URL</a>, <a href=\"%13\">Last.fm</a><br/>" \ "</small>" \ "</div>") .arg( event->imageUrl(LastFmEvent::Medium).url() ) .arg( venue->imageUrls[LastFmEvent::Medium].url() ) .arg( name ) .arg( artists.join(", ") ) .arg( KGlobal::locale()->formatTime( dt.time() ) ) .arg( KGlobal::locale()->formatDate( dt.date(), KLocale::FancyShortDate ) ) .arg( venue->name ) .arg( location ) .arg( desc.isEmpty() ? i18n("none") : desc ) .arg( tags.isEmpty() ? i18n("none") : tags.join(", ") ) .arg( event->url().url() ) .arg( venueWebsite.isEmpty() ? i18n("none") : venueWebsite ) .arg( venueLastFmUrl.isEmpty() ? i18n("none") : venueLastFmUrl ); return html; } -KUrl +QUrl UpcomingEventsMapWidgetPrivate::eventForMapIcon( const LastFmEventPtr &event ) const { // Thanks a whole bunch to Nicolas Mollet, Matthias Stasiak at google-maps-icons // pack (http://code.google.com/p/google-maps-icons/wiki/CultureIcons) const QStringList &tags = event->tags(); QString name; if( tags.contains( "festival", Qt::CaseInsensitive ) ) name = "festival.png"; else if( !tags.filter( QRegExp("rock|metal") ).isEmpty() ) name = "music-rock.png"; else if( !tags.filter( QRegExp("hip.?hop|rap") ).isEmpty() ) name = "music-hiphop.png"; else if( !tags.filter( QRegExp("orchest.*|classical|symphon.*") ).isEmpty() ) name = "music-classical.png"; else if( !tags.filter( QRegExp("choir|chorus|choral") ).isEmpty() ) name = "choral.png"; else if( !tags.filter( QRegExp("danc(e|ing)|disco|electronic") ).isEmpty() ) name = "dancinghall.png"; else name = "music-live.png"; - return KUrl( "http://google-maps-icons.googlecode.com/files/" + name ); + return QUrl( "http://google-maps-icons.googlecode.com/files/" + name ); } void UpcomingEventsMapWidgetPrivate::_centerAt( QObject *obj ) { Q_Q( UpcomingEventsMapWidget ); UpcomingEventsWidget *widget = static_cast<UpcomingEventsWidget*>( obj ); LastFmVenuePtr venue = widget->eventPtr()->venue(); q->centerAt( venue ); } void UpcomingEventsMapWidgetPrivate::_init() { Q_Q( UpcomingEventsMapWidget ); q->connect( q, SIGNAL(loadFinished(bool)), q, SLOT(_loadFinished(bool)) ); QFile mapHtml( KStandardDirs::locate( "data", "amarok/data/upcoming-events-map.html" ) ); if( mapHtml.open( QIODevice::ReadOnly | QIODevice::Text ) ) q->setHtml( mapHtml.readAll() ); } void UpcomingEventsMapWidgetPrivate::_linkClicked( const QUrl &url ) { QDesktopServices::openUrl( url ); } void UpcomingEventsMapWidgetPrivate::_loadFinished( bool success ) { if( !success ) return; Q_Q( UpcomingEventsMapWidget ); isLoaded = true; LastFmEvent::List queue = eventQueue; eventQueue.clear(); foreach( const LastFmEventPtr &event, queue ) addEvent( event ); if( !centerWhenLoaded.isNull() ) { q->centerAt( centerWhenLoaded.y(), centerWhenLoaded.x() ); centerWhenLoaded *= 0.0; } } UpcomingEventsMapWidget::UpcomingEventsMapWidget( QGraphicsItem *parent ) : KGraphicsWebView( parent ) , d_ptr( new UpcomingEventsMapWidgetPrivate( this ) ) { page()->setLinkDelegationPolicy( QWebPage::DelegateAllLinks ); page()->setNetworkAccessManager( The::networkAccessManager() ); connect( page(), SIGNAL(linkClicked(QUrl)), this, SLOT(_linkClicked(QUrl)) ); QTimer::singleShot( 0, this, SLOT(_init()) ); } UpcomingEventsMapWidget::~UpcomingEventsMapWidget() { delete d_ptr; } void UpcomingEventsMapWidget::addEvent( const LastFmEventPtr &event ) { Q_D( UpcomingEventsMapWidget ); d->addEvent( event ); } void UpcomingEventsMapWidget::addEvents( const LastFmEvent::List &events ) { foreach( const LastFmEventPtr &event, events ) addEvent( event ); } void UpcomingEventsMapWidget::addEventsListWidget( UpcomingEventsListWidget *widget ) { Q_D( UpcomingEventsMapWidget ); if( widget ) { d->listWidgets << widget; addEvents( widget->events() ); connect( widget, SIGNAL(eventAdded(LastFmEventPtr)), this, SLOT(addEvent(LastFmEventPtr)) ); connect( widget, SIGNAL(eventRemoved(LastFmEventPtr)), this, SLOT(removeEvent(LastFmEventPtr)) ); connect( widget, SIGNAL(mapRequested(QObject*)), this, SLOT(_centerAt(QObject*)) ); } } void UpcomingEventsMapWidget::removeEventsListWidget( UpcomingEventsListWidget *widget ) { Q_D( UpcomingEventsMapWidget ); if( d->listWidgets.contains( widget ) ) { foreach( const LastFmEventPtr &event, widget->events() ) removeEvent( event ); d->listWidgets.remove( widget ); widget->disconnect( this ); } } void UpcomingEventsMapWidget::removeEvent( const LastFmEventPtr &event ) { Q_D( UpcomingEventsMapWidget ); d->removeEvent( event ); } bool UpcomingEventsMapWidget::isLoaded() const { Q_D( const UpcomingEventsMapWidget ); return d->isLoaded; } int UpcomingEventsMapWidget::eventCount() const { Q_D( const UpcomingEventsMapWidget ); return d->events.count(); } LastFmEvent::List UpcomingEventsMapWidget::events() const { Q_D( const UpcomingEventsMapWidget ); return d->events; } void UpcomingEventsMapWidget::centerAt( double latitude, double longitude ) { Q_D( UpcomingEventsMapWidget ); if( !d->isLoaded ) { QPointF geo( longitude, latitude ); d->centerWhenLoaded = geo; return; } QString lat( QString::number( latitude ) ); QString lng( QString::number( longitude ) ); QString js = QString( "javascript:centerAt(%1,%2)" ).arg( lat ).arg( lng ); page()->mainFrame()->evaluateJavaScript( js ); } void UpcomingEventsMapWidget::centerAt( const LastFmVenuePtr &venue ) { LastFmLocationPtr loc = venue->location; centerAt( loc->latitude, loc->longitude ); } void UpcomingEventsMapWidget::clear() { Q_D( UpcomingEventsMapWidget ); d->events.clear(); page()->mainFrame()->evaluateJavaScript( "javascript:clearMarkers()" ); } diff --git a/src/context/applets/upcomingevents/UpcomingEventsWidget.cpp b/src/context/applets/upcomingevents/UpcomingEventsWidget.cpp index 12276c1272..3e58022f60 100644 --- a/src/context/applets/upcomingevents/UpcomingEventsWidget.cpp +++ b/src/context/applets/upcomingevents/UpcomingEventsWidget.cpp @@ -1,381 +1,381 @@ /**************************************************************************************** * Copyright (c) 2009-2010 Ludovic Deveaux <deveaux.ludovic31@gmail.com> * * Copyright (c) 2010 Hormiere Guillaume <hormiere.guillaume@gmail.com> * * * * 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, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ #define DEBUG_PREFIX "UpcomingEventsWidget" #include "UpcomingEventsWidget.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include "SvgHandler.h" #include <KDateTime> #include <KIcon> #include <KLocale> #include <Plasma/Label> #include <Plasma/PushButton> #include <Plasma/Separator> #include <QDesktopServices> #include <QLabel> #include <QPixmap> #include <QPixmapCache> #include <QGraphicsGridLayout> #include <QGraphicsLinearLayout> #include <QGraphicsProxyWidget> #include <QSignalMapper> UpcomingEventsWidget::UpcomingEventsWidget( const LastFmEventPtr &event, QGraphicsItem *parent, Qt::WindowFlags wFlags ) : QGraphicsWidget( parent, wFlags ) , m_mapButton( 0 ) , m_urlButton( 0 ) , m_image( new QLabel ) , m_event( event ) { setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Maximum ); m_image->setText( i18n("Loading picture...") ); m_image->setAttribute( Qt::WA_NoSystemBackground ); m_image->setAlignment( Qt::AlignCenter ); m_image->setFixedSize( 128, 128 ); QGraphicsProxyWidget *imageProxy = new QGraphicsProxyWidget( this ); imageProxy->setWidget( m_image ); m_attendance = createLabel(); m_date = createLabel(); m_location = createLabel(); m_name = createLabel(); m_participants = createLabel(); m_tags = createLabel(); m_venue = createLabel(); QGraphicsLinearLayout *buttonsLayout = new QGraphicsLinearLayout( Qt::Horizontal ); buttonsLayout->setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Fixed ); if( event && event->venue() && event->venue()->location ) { QPointF geo( event->venue()->location->longitude, event->venue()->location->latitude ); if( !geo.isNull() ) { m_mapButton = new Plasma::PushButton( this ); m_mapButton->setMaximumSize( QSizeF( 22, 22 ) ); m_mapButton->setIcon( KIcon("edit-find") ); // TODO: a map icon would be nice m_mapButton->setToolTip( i18n( "View map" ) ); buttonsLayout->addItem( m_mapButton ); } } if( event && event->url().isValid() ) { m_urlButton = new Plasma::PushButton( this ); m_urlButton->setMaximumSize( QSizeF( 22, 22 ) ); m_urlButton->setIcon( KIcon("applications-internet") ); m_urlButton->setToolTip( i18n( "Open Last.fm webpage for this event" ) ); connect( m_urlButton, SIGNAL(clicked()), this, SLOT(openUrl()) ); buttonsLayout->addItem( m_urlButton ); } QSizePolicy::Policy minPol = QSizePolicy::Minimum; QGraphicsWidget *supportLabel, *venueLabel, *locationLabel, *dateLabel, *attendLabel, *tagsLabel; supportLabel = createLabel( i18nc("@label:textbox Supporing acts for an event", "Supporting:"), minPol ); venueLabel = createLabel( i18nc("@label:textbox", "Venue:"), minPol ); locationLabel = createLabel( i18nc("@label:textbox", "Location:"), minPol ); dateLabel = createLabel( i18nc("@label:textbox", "Date:"), minPol ); attendLabel = createLabel( i18nc("@label:textbox", "Attending:"), minPol ); tagsLabel = createLabel( i18nc("@label:textbox", "Tags:"), minPol ); QGraphicsGridLayout *infoLayout = new QGraphicsGridLayout; infoLayout->addItem( supportLabel, 0, 0 ); infoLayout->addItem( venueLabel, 1, 0 ); infoLayout->addItem( locationLabel, 2, 0 ); infoLayout->addItem( dateLabel, 3, 0 ); infoLayout->addItem( attendLabel, 4, 0 ); infoLayout->addItem( tagsLabel, 5, 0 ); infoLayout->addItem( m_participants, 0, 1 ); infoLayout->addItem( m_venue, 1, 1 ); infoLayout->addItem( m_location, 2, 1 ); infoLayout->addItem( m_date, 3, 1 ); infoLayout->addItem( m_attendance, 4, 1 ); infoLayout->addItem( m_tags, 5, 1 ); QGraphicsGridLayout *layout = new QGraphicsGridLayout; layout->addItem( imageProxy, 0, 0, 2, 1, Qt::AlignCenter ); layout->addItem( m_name, 0, 1 ); layout->addItem( buttonsLayout, 0, 2, Qt::AlignRight ); layout->addItem( infoLayout, 1, 1, 1, 2 ); setLayout( layout ); QString name = event->name(); if( event->isCancelled() ) name = i18nc( "@label:textbox Title for a canceled upcoming event", "<s>%1</s> (Canceled)", name ); setName( name ); setDate( event->date() ); setLocation( event->venue()->location ); setVenue( event->venue() ); setAttendance( event->attendance() ); setParticipants( event->participants() ); setTags( event->tags() ); setImage( event->imageUrl(LastFmEvent::Large) ); } UpcomingEventsWidget::~UpcomingEventsWidget() { } QGraphicsProxyWidget * UpcomingEventsWidget::createLabel( const QString &text, QSizePolicy::Policy hPolicy ) { QLabel *label = new QLabel; label->setAttribute( Qt::WA_NoSystemBackground ); label->setMinimumWidth( 10 ); label->setSizePolicy( hPolicy, QSizePolicy::Preferred ); label->setText( text ); label->setWordWrap( false ); QGraphicsProxyWidget *proxy = new QGraphicsProxyWidget( this ); proxy->setWidget( label ); return proxy; } void -UpcomingEventsWidget::setImage( const KUrl &url ) +UpcomingEventsWidget::setImage( const QUrl &url ) { if( url.isValid() ) { m_imageUrl = url; QPixmap pixmap; if( QPixmapCache::find(url.url(), &pixmap) ) { m_image->setPixmap( pixmap ); return; } QNetworkReply *reply = The::networkAccessManager()->get( QNetworkRequest(url) ); connect( reply, SIGNAL(finished()), SLOT(loadImage()), Qt::QueuedConnection ); } m_image->setPixmap( Amarok::semiTransparentLogo( 120 ) ); } void UpcomingEventsWidget::loadImage() { QNetworkReply *reply = qobject_cast<QNetworkReply*>( sender() ); if( !reply ) return; reply->deleteLater(); - const KUrl &url = reply->request().url(); + const QUrl &url = reply->request().url(); if( m_imageUrl != url ) return; if( reply->error() != QNetworkReply::NoError ) return; QPixmap image; if( image.loadFromData( reply->readAll() ) ) { image = image.scaled( 116, 116, Qt::KeepAspectRatio, Qt::SmoothTransformation ); image = The::svgHandler()->addBordersToPixmap( image, 6, QString(), true ); QPixmapCache::insert( url.url(), image ); m_image->setPixmap( image ); } } void UpcomingEventsWidget::openUrl() { if( m_event->url().isValid() ) QDesktopServices::openUrl( m_event->url() ); } void UpcomingEventsWidget::setTags( const QStringList &tags ) { QLabel *tagsLabel = static_cast<QLabel*>( m_tags->widget() ); tagsLabel->setText( tags.isEmpty() ? i18n( "none" ) : tags.join(", ") ); QStringList tooltips; if( tags.count() > 10 ) { for( int i = 0; i < 10; ++i ) tooltips << tags.value( i ); } else tooltips = tags; tagsLabel->setToolTip( i18nc( "@info:tooltip", "<strong>Tags:</strong><nl/>%1", tooltips.join(", ") ) ); } void UpcomingEventsWidget::setAttendance( int count ) { static_cast<QLabel*>(m_attendance->widget())->setText( QString::number(count) ); } void UpcomingEventsWidget::setParticipants( const QStringList &participants ) { QLabel *participantsLabel = static_cast<QLabel*>( m_participants->widget() ); if( participants.isEmpty() ) { participantsLabel->setText( i18n( "none" ) ); } else { QString combined = participants.join( ", " ); participantsLabel->setText( combined ); if( participants.size() > 1 ) { QString tooltip = i18nc( "@info:tooltip Supporting artists for an event", "<strong>Supporting artists:</strong><nl/>%1", combined ); participantsLabel->setToolTip( tooltip ); } } } void UpcomingEventsWidget::setDate( const KDateTime &date ) { QLabel *dateLabel = static_cast<QLabel*>( m_date->widget() ); dateLabel->setText( KGlobal::locale()->formatDateTime( date, KLocale::FancyLongDate ) ); KDateTime currentDT = KDateTime::currentLocalDateTime(); if( currentDT.compare(date) == KDateTime::Before ) { int daysTo = currentDT.daysTo( date ); dateLabel->setToolTip( i18ncp( "@info:tooltip Number of days till an event", "Tomorrow", "In <strong>%1</strong> days", daysTo ) ); } } void UpcomingEventsWidget::setLocation( const LastFmLocationPtr &loc ) { QString text = QString( "%1, %2" ).arg( loc->city, loc->country ); if( !loc->street.isEmpty() ) text.prepend( loc->street + ", " ); QLabel *locLabel = static_cast<QLabel*>( m_location->widget() ); locLabel->setText( text ); locLabel->setToolTip( i18nc( "@info:tooltip", "<strong>Location:</strong><nl/>%1", text ) ); } void UpcomingEventsWidget::setVenue( const LastFmVenuePtr &venue ) { static_cast<QLabel*>( m_venue->widget() )->setText( venue->name ); } void UpcomingEventsWidget::setName( const QString &name ) { QLabel *nameLabel = static_cast<QLabel*>( m_name->widget() ); QFont nameFont; nameFont.setBold( true ); nameFont.setPointSize( nameLabel->font().pointSize() + 2 ); nameLabel->setFont( nameFont ); nameLabel->setText( name ); } UpcomingEventsListWidget::UpcomingEventsListWidget( QGraphicsWidget *parent ) : Plasma::ScrollWidget( parent ) , m_sigmap( new QSignalMapper( this ) ) { // The widgets are displayed line by line with only one column m_layout = new QGraphicsLinearLayout( Qt::Vertical ); QGraphicsWidget *content = new QGraphicsWidget( this ); content->setLayout( m_layout ); setWidget( content ); connect( m_sigmap, SIGNAL(mapped(QObject*)), this, SIGNAL(mapRequested(QObject*)) ); } UpcomingEventsListWidget::~UpcomingEventsListWidget() { clear(); } int UpcomingEventsListWidget::count() const { return m_events.count(); } LastFmEvent::List UpcomingEventsListWidget::events() const { return m_events; } void UpcomingEventsListWidget::addEvent( const LastFmEventPtr &event ) { m_events << event; UpcomingEventsWidget *widget = new UpcomingEventsWidget( event ); const uint eventTime = event->date().toTime_t(); QMap<uint, UpcomingEventsWidget*>::const_iterator iBound( m_sortMap.insertMulti( eventTime, widget ) ); QMap<uint, UpcomingEventsWidget*>::const_iterator i( m_sortMap.constBegin() ); int index = 0; while( i++ != iBound ) ++index; // find the right index to insert the widget index *= 2; // take separators into account m_layout->insertItem( index, widget ); m_layout->insertItem( index + 1, new Plasma::Separator ); if( widget->m_mapButton ) { connect( widget->m_mapButton, SIGNAL(clicked()), m_sigmap, SLOT(map()) ); m_sigmap->setMapping( widget->m_mapButton, widget ); } emit eventAdded( event ); } void UpcomingEventsListWidget::addEvents( const LastFmEvent::List &events ) { foreach( const LastFmEventPtr &event, events ) addEvent( event ); } void UpcomingEventsListWidget::clear() { foreach( const LastFmEventPtr &event, m_events ) emit eventRemoved( event ); m_events.clear(); qDeleteAll( m_sortMap.values() ); m_sortMap.clear(); int count = m_layout->count(); while( --count >= 0 ) { QGraphicsLayoutItem *child = m_layout->itemAt( 0 ); m_layout->removeItem( child ); delete child; } } bool UpcomingEventsListWidget::isEmpty() const { return count() == 0; } QString UpcomingEventsListWidget::name() const { return m_name; } void UpcomingEventsListWidget::setName( const QString &name ) { m_name = name; } diff --git a/src/context/applets/upcomingevents/UpcomingEventsWidget.h b/src/context/applets/upcomingevents/UpcomingEventsWidget.h index 0b414da695..e9b3275f13 100644 --- a/src/context/applets/upcomingevents/UpcomingEventsWidget.h +++ b/src/context/applets/upcomingevents/UpcomingEventsWidget.h @@ -1,177 +1,177 @@ /**************************************************************************************** * Copyright (c) 2009-2010 Ludovic Deveaux <deveaux.ludovic31@gmail.com> * * Copyright (c) 2010 Hormiere Guillaume <hormiere.guillaume@gmail.com> * * * * 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, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ #ifndef UPCOMING_EVENTS_WIDGET_H #define UPCOMING_EVENTS_WIDGET_H #include "NetworkAccessManagerProxy.h" #include "LastFmEvent.h" -#include <KUrl> +#include <QUrl> #include <Plasma/ScrollWidget> #include <QGraphicsWidget> class KDateTime; class QLabel; class QGraphicsLinearLayout; class QGraphicsProxyWidget; class QPixmap; class QPointF; class QSignalMapper; namespace Plasma { class Label; class PushButton; } class UpcomingEventsWidget : public QGraphicsWidget { Q_OBJECT public: /** * UpcomingEventsWidget constructor * @param QGraphicsWidget*, like QGraphicsWidget constructor */ UpcomingEventsWidget( const LastFmEventPtr &event, QGraphicsItem *parent = 0, Qt::WindowFlags wFlags = 0 ); ~UpcomingEventsWidget(); /** * The upcoming event associated with this widget */ LastFmEventPtr eventPtr() const { return m_event; } /** *Set the event's image in Plasma::Label from an url - *@param KUrl, image's url to be displayed + *@param QUrl, image's url to be displayed */ - void setImage( const KUrl &url ); + void setImage( const QUrl &url ); /** * Set attendance for this event * @param count number of attendees */ void setAttendance( int count ); /** *Set the event's participants text in Plasma::Label from a QString *@param QString, participant's text to be displayed */ void setParticipants( const QStringList &participants ); /** *Set the event's date in Plasma::Label from a KDateTime *@param KDateTime, date to be displayed */ void setDate( const KDateTime &date ); /** *Set the event's name in Plasma::Label from a QString *@param QString, name's text to be displayed */ void setName( const QString &name ); /** *Set the event's location in a Plasma::Label from a QString *@param QString, location's text to be displayed */ void setLocation( const LastFmLocationPtr &location ); /** * Set event venue * @param venue Last.fm's venue */ void setVenue( const LastFmVenuePtr &venue ); /** - *Set the event's url in Plasma::Label from a KUrl - *@param KUrl, url to be displayed + *Set the event's url in Plasma::Label from a QUrl + *@param QUrl, url to be displayed */ - void setUrl( const KUrl &url ); + void setUrl( const QUrl &url ); /** * Set the events tags * @param tags list of tags */ void setTags( const QStringList &tags ); protected: Plasma::PushButton *m_mapButton; private: Plasma::PushButton *m_urlButton; QGraphicsProxyWidget *m_attendance; QGraphicsProxyWidget *m_date; QGraphicsProxyWidget *m_location; QGraphicsProxyWidget *m_name; QGraphicsProxyWidget *m_participants; QGraphicsProxyWidget *m_tags; QGraphicsProxyWidget *m_venue; QLabel *m_image; - KUrl m_imageUrl; + QUrl m_imageUrl; const LastFmEventPtr m_event; QGraphicsProxyWidget *createLabel( const QString &text = QString(), QSizePolicy::Policy hPolicy = QSizePolicy::Expanding ); friend class UpcomingEventsListWidget; private slots: void loadImage(); void openUrl(); }; class UpcomingEventsListWidget : public Plasma::ScrollWidget { Q_OBJECT Q_PROPERTY( QString name READ name WRITE setName ) Q_PROPERTY( LastFmEvent::List events READ events ) public: explicit UpcomingEventsListWidget( QGraphicsWidget *parent = 0 ); ~UpcomingEventsListWidget(); int count() const; bool isEmpty() const; void addEvent( const LastFmEventPtr &event ); void addEvents( const LastFmEvent::List &events ); LastFmEvent::List events() const; QString name() const; void setName( const QString &name ); void clear(); signals: void mapRequested( QObject *widget ); void eventAdded( const LastFmEventPtr &event ); void eventRemoved( const LastFmEventPtr &event ); private: QString m_name; LastFmEvent::List m_events; QMap<uint, UpcomingEventsWidget*> m_sortMap; QGraphicsLinearLayout *m_layout; QSignalMapper *m_sigmap; Q_DISABLE_COPY( UpcomingEventsListWidget ) }; #endif /* UPCOMINGEVENTSWIDGET_H */ diff --git a/src/context/applets/wikipedia/WikipediaApplet.cpp b/src/context/applets/wikipedia/WikipediaApplet.cpp index 44abe0a198..d96f93a8c7 100644 --- a/src/context/applets/wikipedia/WikipediaApplet.cpp +++ b/src/context/applets/wikipedia/WikipediaApplet.cpp @@ -1,804 +1,804 @@ /**************************************************************************************** * Copyright (c) 2007 Leo Franchi <lfranchi@gmail.com> * * Copyright (c) 2009 Simon Esneault <simon.esneault@gmail.com> * * * * 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, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ #define DEBUG_PREFIX "WikipediaApplet" #include "WikipediaApplet.h" #include "WikipediaApplet_p.h" #include "WikipediaApplet_p.moc" #include "App.h" #include "core/support/Amarok.h" #include "context/widgets/AppletHeader.h" #include "core/support/Debug.h" #include "PaletteHandler.h" #include <KConfigDialog> #include <KGlobalSettings> #include <KSaveFile> #include <KStandardDirs> #include <KTemporaryFile> #include <Plasma/DataContainer> #include <Plasma/IconWidget> #include <Plasma/Theme> #include <QAction> #include <QTimer> #include <QDesktopServices> #include <QGraphicsLinearLayout> #include <QGraphicsView> #include <QListWidget> #include <QPainter> #include <QProgressBar> #include <QTextStream> #include <QWebPage> #include <QXmlStreamReader> void WikipediaAppletPrivate::parseWikiLangXml( const QByteArray &data ) { QXmlStreamReader xml( data ); while( !xml.atEnd() && !xml.hasError() ) { xml.readNext(); if( xml.isStartElement() && xml.name() == "iw" ) { const QXmlStreamAttributes &a = xml.attributes(); if( a.hasAttribute("prefix") && a.hasAttribute("language") && a.hasAttribute("url") ) { const QString &prefix = a.value("prefix").toString(); const QString &language = a.value("language").toString(); const QString &display = QString( "[%1] %2" ).arg( prefix, language ); QListWidgetItem *item = new QListWidgetItem( display, 0 ); // The urlPrefix is the lang code infront of the wikipedia host // url. It is mostly the same as the "prefix" attribute but in // some weird cases they differ, so we can't just use "prefix". QString urlPrefix = QUrl( a.value("url").toString() ).host().remove(".wikipedia.org"); item->setData( PrefixRole, prefix ); item->setData( UrlPrefixRole, urlPrefix ); item->setData( LanguageStringRole, language ); languageSettingsUi.langSelector->availableListWidget()->addItem( item ); } } } } qint64 WikipediaAppletPrivate::writeStyleSheet( const QByteArray &data ) { delete css; css = new KTemporaryFile(); css->setSuffix( ".css" ); qint64 written( -1 ); if( css->open() ) { written = css->write( data ); // NOTE shall we keep this commented out, and bring it back later or the base64 is just what we need ? // QString filename = css->fileName(); css->close(); // flush buffer to disk // debug() << "set user stylesheet to:" << "file://" + filename; // webView->page()->settings()->setUserStyleSheetUrl( "file://" + filename ); } return written; } void WikipediaAppletPrivate::scheduleEngineUpdate() { Q_Q( WikipediaApplet ); q->dataEngine( "amarok-wikipedia" )->query( "update" ); } void WikipediaAppletPrivate::setUrl( const QUrl &url ) { webView->settings()->resetFontSize( QWebSettings::MinimumFontSize ); webView->settings()->resetFontSize( QWebSettings::MinimumLogicalFontSize ); webView->settings()->resetFontSize( QWebSettings::DefaultFontSize ); webView->settings()->resetFontSize( QWebSettings::DefaultFixedFontSize ); webView->settings()->resetFontFamily( QWebSettings::StandardFont ); webView->setUrl( url ); currentUrl = url; dataContainer->removeAllData(); } void WikipediaAppletPrivate::pushUrlHistory( const QUrl &url ) { if( !isForwardHistory && !isBackwardHistory && !url.isEmpty() ) { if( historyBack.isEmpty() || (!historyBack.isEmpty() && (url != historyBack.top())) ) historyBack.push( url ); historyForward.clear(); } isBackwardHistory = false; isForwardHistory = false; updateNavigationIcons(); } void WikipediaAppletPrivate::updateNavigationIcons() { forwardIcon->action()->setEnabled( !historyForward.isEmpty() ); backwardIcon->action()->setEnabled( !historyBack.isEmpty() ); } void WikipediaAppletPrivate::_titleChanged( const QString &title ) { Q_Q( WikipediaApplet ); q->setHeaderText( title ); } void WikipediaAppletPrivate::_goBackward() { DEBUG_BLOCK if( !historyBack.empty() ) { historyForward.push( currentUrl ); currentUrl = historyBack.pop(); isBackwardHistory = true; dataContainer->removeAllData(); dataContainer->setData( "clickUrl", currentUrl ); scheduleEngineUpdate(); updateNavigationIcons(); } } void WikipediaAppletPrivate::_goForward() { DEBUG_BLOCK if( !historyForward.empty() ) { historyBack.push( currentUrl ); currentUrl = historyForward.pop(); isForwardHistory = true; dataContainer->removeAllData(); dataContainer->setData( "clickUrl", currentUrl ); scheduleEngineUpdate(); updateNavigationIcons(); } } void WikipediaAppletPrivate::_gotoAlbum() { dataContainer->setData( "goto", "album" ); scheduleEngineUpdate(); } void WikipediaAppletPrivate::_gotoArtist() { dataContainer->setData( "goto", "artist" ); scheduleEngineUpdate(); } void WikipediaAppletPrivate::_gotoComposer() { dataContainer->setData( "goto", "composer" ); scheduleEngineUpdate(); } void WikipediaAppletPrivate::_gotoTrack() { dataContainer->setData( "goto", "track" ); scheduleEngineUpdate(); } void WikipediaAppletPrivate::_jsWindowObjectCleared() { Q_Q( WikipediaApplet ); webView->page()->mainFrame()->addToJavaScriptWindowObject( "mWebPage", q ); } void WikipediaAppletPrivate::_linkClicked( const QUrl &url ) { DEBUG_BLOCK Q_Q( WikipediaApplet ); if( url.host().contains( "wikipedia.org" ) ) { isBackwardHistory = false; isForwardHistory = false; pushUrlHistory( currentUrl ); if( useMobileWikipedia ) { setUrl( url ); return; } q->setBusy( true ); dataContainer->setData( "clickUrl", url ); scheduleEngineUpdate(); } else QDesktopServices::openUrl( url.toString() ); } void WikipediaAppletPrivate::_loadSettings() { QStringList list; QListWidget *listWidget = languageSettingsUi.langSelector->selectedListWidget(); for( int i = 0, count = listWidget->count(); i < count; ++i ) { QListWidgetItem *item = listWidget->item( i ); const QString &prefix = item->data( PrefixRole ).toString(); const QString &urlPrefix = item->data( UrlPrefixRole ).toString(); QString concat = QString("%1:%2").arg( prefix, urlPrefix ); list << (prefix == urlPrefix ? prefix : concat); } langList = list; useMobileWikipedia = (generalSettingsUi.mobileCheckBox->checkState() == Qt::Checked); useSSL = (generalSettingsUi.sslCheckBox->checkState() == Qt::Checked); Amarok::config("Wikipedia Applet").writeEntry( "PreferredLang", list ); Amarok::config("Wikipedia Applet").writeEntry( "UseMobile", useMobileWikipedia ); Amarok::config( "Wikipedia Applet" ).writeEntry( "UseSSL", useSSL ); _paletteChanged( App::instance()->palette() ); dataContainer->setData( "lang", langList ); dataContainer->setData( "mobile", useMobileWikipedia ); dataContainer->setData( "ssl", useSSL ); scheduleEngineUpdate(); } void WikipediaAppletPrivate::_paletteChanged( const QPalette &palette ) { if( useMobileWikipedia ) { webView->settings()->setUserStyleSheetUrl( QUrl() ); return; } // read css, replace color placeholders, write to file, load into page QFile file( KStandardDirs::locate("data", "amarok/data/WikipediaCustomStyle.css" ) ); if( file.open(QIODevice::ReadOnly | QIODevice::Text) ) { // transparent background QPalette newPalette( palette ); newPalette.setBrush( QPalette::Base, Qt::transparent ); webView->page()->setPalette( newPalette ); webView->setPalette( newPalette ); webView->setAttribute( Qt::WA_OpaquePaintEvent, false ); QString contents = QString( file.readAll() ); contents.replace( "/*{text_color}*/", palette.text().color().name() ); contents.replace( "/*{link_color}*/", palette.link().color().name() ); contents.replace( "/*{link_hover_color}*/", palette.linkVisited().color().name() ); const QString abg = The::paletteHandler()->alternateBackgroundColor().name(); contents.replace( "/*{shaded_text_background_color}*/", abg ); contents.replace( "/*{table_background_color}*/", abg ); contents.replace( "/*{headings_background_color}*/", abg ); const QString hiColor = The::paletteHandler()->highlightColor().name(); contents.replace( "/*{border_color}*/", hiColor ); const QString atbg = palette.highlight().color().name(); contents.replace( "/*{alternate_table_background_color}*/", atbg ); const QByteArray &css = contents.toLatin1(); qint64 written = writeStyleSheet( css ); if( written != -1 ) { QUrl cssUrl( QString("data:text/css;charset=utf-8;base64,") + css.toBase64() ); //NOTE We give it encoded on a base64 // as it is currently broken on QtWebkit (see https://bugs.webkit.org/show_bug.cgi?id=34884 ) webView->settings()->setUserStyleSheetUrl( cssUrl ); } } } void WikipediaAppletPrivate::_reloadWikipedia() { DEBUG_BLOCK if( useMobileWikipedia ) { webView->reload(); return; } dataContainer->setData( "reload", true ); scheduleEngineUpdate(); } void WikipediaAppletPrivate::_updateWebFonts() { Q_Q( WikipediaApplet ); if( !q->view() ) return; qreal ratio = q->view()->logicalDpiY() / 72.0; qreal fixedFontSize = KGlobalSettings::fixedFont().pointSize() * ratio; qreal generalFontSize = KGlobalSettings::generalFont().pointSize() * ratio; qreal minimumFontSize = KGlobalSettings::smallestReadableFont().pointSize() * ratio; QWebSettings *webSettings = webView->page()->settings(); webSettings->setFontSize( QWebSettings::DefaultFixedFontSize, qRound(fixedFontSize) ); webSettings->setFontSize( QWebSettings::DefaultFontSize, qRound(generalFontSize) ); webSettings->setFontSize( QWebSettings::MinimumFontSize, qRound(minimumFontSize) ); webSettings->setFontFamily( QWebSettings::StandardFont, KGlobalSettings::generalFont().family() ); } void WikipediaAppletPrivate::_getLangMapProgress( qint64 received, qint64 total ) { languageSettingsUi.progressBar->setValue( 100.0 * qreal(received) / total ); } void -WikipediaAppletPrivate::_getLangMapFinished( const KUrl &url, QByteArray data, +WikipediaAppletPrivate::_getLangMapFinished( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ) { Q_UNUSED( url ) languageSettingsUi.downloadButton->setEnabled( true ); languageSettingsUi.progressBar->setEnabled( false ); if( e.code != QNetworkReply::NoError ) { debug() << "Downloading Wikipedia supported languages failed:" << e.description; return; } QListWidget *availableListWidget = languageSettingsUi.langSelector->availableListWidget(); availableListWidget->clear(); parseWikiLangXml( data ); languageSettingsUi.langSelector->setButtonsEnabled(); QString buttonText = ( availableListWidget->count() > 0 ) ? i18n( "Update Supported Languages" ) : i18n( "Get Supported Languages" ); languageSettingsUi.downloadButton->setText( buttonText ); QListWidget *selectedListWidget = languageSettingsUi.langSelector->selectedListWidget(); QList<QListWidgetItem*> selectedListItems = selectedListWidget->findItems( QChar('*'), Qt::MatchWildcard ); for( int i = 0, count = selectedListItems.count(); i < count; ++i ) { QListWidgetItem *item = selectedListItems.at( i ); int rowAtSelectedList = selectedListWidget->row( item ); item = selectedListWidget->takeItem( rowAtSelectedList ); const QString &prefix = item->data( PrefixRole ).toString(); QList<QListWidgetItem*> foundItems = availableListWidget->findItems( QString("[%1]").arg( prefix ), Qt::MatchStartsWith ); // should only have found one item if any if( !foundItems.isEmpty() ) { item = foundItems.first(); int rowAtAvailableList = languageSettingsUi.langSelector->availableListWidget()->row( item ); item = availableListWidget->takeItem( rowAtAvailableList ); selectedListWidget->addItem( item ); } } KSaveFile saveFile; saveFile.setFileName( Amarok::saveLocation() + "wikipedia_languages.xml" ); if( saveFile.open() ) { debug() << "Saving" << saveFile.fileName(); QTextStream stream( &saveFile ); stream << data; stream.flush(); saveFile.finalize(); } else { debug() << "Failed to saving Wikipedia languages file"; } } void WikipediaAppletPrivate::_getLangMap() { Q_Q( WikipediaApplet ); languageSettingsUi.downloadButton->setEnabled( false ); languageSettingsUi.progressBar->setEnabled( true ); languageSettingsUi.progressBar->setMaximum( 100 ); languageSettingsUi.progressBar->setValue( 0 ); - KUrl url; + QUrl url; url.setScheme( "http" ); url.setHost( "en.wikipedia.org" ); url.setPath( "/w/api.php" ); url.addQueryItem( "action", "query" ); url.addQueryItem( "meta", "siteinfo" ); url.addQueryItem( "siprop", "interwikimap" ); url.addQueryItem( "sifilteriw", "local" ); url.addQueryItem( "format", "xml" ); QNetworkReply *reply = The::networkAccessManager()->getData( url, q, - SLOT(_getLangMapFinished(KUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); + SLOT(_getLangMapFinished(QUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); q->connect( reply, SIGNAL(downloadProgress(qint64,qint64)), q, SLOT(_getLangMapProgress(qint64,qint64)) ); } void WikipediaAppletPrivate::_configureLangSelector() { DEBUG_BLOCK Q_Q( WikipediaApplet ); QFile savedFile( Amarok::saveLocation() + "wikipedia_languages.xml" ); if( savedFile.open(QIODevice::ReadOnly | QIODevice::Text) ) parseWikiLangXml( savedFile.readAll() ); savedFile.close(); QListWidget *availableListWidget = languageSettingsUi.langSelector->availableListWidget(); QString buttonText = ( availableListWidget->count() > 0 ) ? i18n( "Update Supported Languages" ) : i18n( "Get Supported Languages" ); languageSettingsUi.downloadButton->setText( buttonText ); for( int i = 0, count = langList.count(); i < count; ++i ) { const QStringList &split = langList.at( i ).split( QLatin1Char(':') ); const QString &prefix = split.first(); const QString &urlPrefix = (split.count() == 1) ? prefix : split.at( 1 ); QList<QListWidgetItem*> foundItems = availableListWidget->findItems( QString("[%1]").arg( prefix ), Qt::MatchStartsWith ); if( foundItems.isEmpty() ) { QListWidgetItem *item = new QListWidgetItem( prefix, 0 ); item->setData( WikipediaAppletPrivate::PrefixRole, prefix ); item->setData( WikipediaAppletPrivate::UrlPrefixRole, urlPrefix ); languageSettingsUi.langSelector->selectedListWidget()->addItem( item ); } else // should only have found one item if any { QListWidgetItem *item = foundItems.first(); int rowAtAvailableList = availableListWidget->row( item ); item = availableListWidget->takeItem( rowAtAvailableList ); languageSettingsUi.langSelector->selectedListWidget()->addItem( item ); } } q->connect( languageSettingsUi.langSelector, SIGNAL(added(QListWidgetItem*)), q, SLOT(_langSelectorItemChanged(QListWidgetItem*)) ); q->connect( languageSettingsUi.langSelector, SIGNAL(movedDown(QListWidgetItem*)), q, SLOT(_langSelectorItemChanged(QListWidgetItem*)) ); q->connect( languageSettingsUi.langSelector, SIGNAL(movedUp(QListWidgetItem*)), q, SLOT(_langSelectorItemChanged(QListWidgetItem*)) ); q->connect( languageSettingsUi.langSelector, SIGNAL(removed(QListWidgetItem*)), q, SLOT(_langSelectorItemChanged(QListWidgetItem*)) ); q->connect( languageSettingsUi.langSelector->availableListWidget(), SIGNAL(itemClicked(QListWidgetItem*)), q, SLOT(_langSelectorItemChanged(QListWidgetItem*)) ); q->connect( languageSettingsUi.langSelector->selectedListWidget(), SIGNAL(itemClicked(QListWidgetItem*)), q, SLOT(_langSelectorItemChanged(QListWidgetItem*)) ); } void WikipediaAppletPrivate::_pageLoadStarted() { Q_Q( WikipediaApplet ); // create a proxy widget for displaying the webview load status in form of a progress bar // if the proxyWidget still exists, re-use the existing object if ( proxyWidget ) return; proxyWidget = new QGraphicsProxyWidget; proxyWidget->setWidget( new QProgressBar ); // add proxy widget to layout QGraphicsLinearLayout *lo = static_cast<QGraphicsLinearLayout*>( q->layout() ); lo->addItem( proxyWidget ); lo->activate(); QObject::connect( webView, SIGNAL(loadProgress(int)), q, SLOT(_pageLoadProgress(int)) ); } void WikipediaAppletPrivate::_pageLoadProgress( int progress ) { DEBUG_ASSERT(proxyWidget, return) const QString kbytes = QString::number( webView->page()->totalBytes() / 1024 ); QProgressBar *pbar = qobject_cast<QProgressBar*>( proxyWidget->widget() ); pbar->setFormat( QString( "%1kB : %p%" ).arg( kbytes ) ); pbar->setValue( progress ); } void WikipediaAppletPrivate::_pageLoadFinished( bool ok ) { Q_UNUSED( ok ) Q_Q( WikipediaApplet ); // remove proxy widget from layout again, delete it QGraphicsLinearLayout *lo = static_cast<QGraphicsLinearLayout*>( q->layout() ); lo->removeItem( proxyWidget ); lo->activate(); // disconnect (so that we don't get any further progress signalling) and delete widget QObject::disconnect( webView, SIGNAL(loadProgress(int)), q, SLOT(_pageLoadProgress(int)) ); proxyWidget->deleteLater(); proxyWidget = 0; } void WikipediaAppletPrivate::_searchLineEditTextEdited( const QString &text ) { webView->page()->findText( QString(), QWebPage::HighlightAllOccurrences ); // clears preivous highlights webView->page()->findText( text, QWebPage::FindWrapsAroundDocument | QWebPage::HighlightAllOccurrences ); } void WikipediaAppletPrivate::_searchLineEditReturnPressed() { const QString &text = webView->lineEdit()->text(); webView->page()->findText( text, QWebPage::FindWrapsAroundDocument ); } void WikipediaAppletPrivate::_langSelectorItemChanged( QListWidgetItem *item ) { Q_UNUSED( item ) languageSettingsUi.langSelector->setButtonsEnabled(); } WikipediaApplet::WikipediaApplet( QObject* parent, const QVariantList& args ) : Context::Applet( parent, args ) , d_ptr( new WikipediaAppletPrivate( this ) ) { setHasConfigurationInterface( true ); } WikipediaApplet::~WikipediaApplet() { Q_D( WikipediaApplet ); delete d->webView; delete d->css; delete d_ptr; } void WikipediaApplet::init() { DEBUG_BLOCK Context::Applet::init(); Q_D( WikipediaApplet ); d->webView = new WikipediaWebView( this ); d->webView->page()->mainFrame()->setScrollBarPolicy( Qt::Horizontal, Qt::ScrollBarAlwaysOff ); // d->webView->page()->mainFrame()->addToJavaScriptWindowObject( "mWebPage", this ); BUG:259075 d->webView->page()->setNetworkAccessManager( The::networkAccessManager() ); d->webView->page()->setLinkDelegationPolicy ( QWebPage::DelegateAllLinks ); d->webView->page()->settings()->setAttribute( QWebSettings::PrivateBrowsingEnabled, true ); QWebSettings::globalSettings()->setAttribute( QWebSettings::DnsPrefetchEnabled, true ); d->webView->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ); d->_updateWebFonts(); connect( KGlobalSettings::self(), SIGNAL(appearanceChanged()), SLOT(_updateWebFonts()) ); connect( The::paletteHandler(), SIGNAL(newPalette(QPalette)), SLOT(_paletteChanged(QPalette)) ); connect( d->webView->page(), SIGNAL(linkClicked(QUrl)), SLOT(_linkClicked(QUrl)) ); connect( d->webView->page(), SIGNAL(loadStarted()), SLOT(_pageLoadStarted()) ); connect( d->webView->page(), SIGNAL(loadFinished(bool)), SLOT(_pageLoadFinished(bool)) ); // connect( d->webView->page()->mainFrame(), SIGNAL(javaScriptWindowObjectCleared()), SLOT(_jsWindowObjectCleared()) ); BUG:259075 connect( d->webView->lineEdit(), SIGNAL(textChanged(QString)), SLOT(_searchLineEditTextEdited(QString)) ); connect( d->webView->lineEdit(), SIGNAL(returnPressed()), SLOT(_searchLineEditReturnPressed()) ); connect( d->webView, SIGNAL(titleChanged(QString)), this, SLOT(_titleChanged(QString)) ); enableHeader( true ); setHeaderText( i18n( "Wikipedia" ) ); setCollapseOffHeight( -1 ); setCollapseHeight( m_header->height() ); setMinimumHeight( collapseHeight() ); setPreferredHeight( collapseHeight() ); QAction* backwardAction = new QAction( this ); backwardAction->setIcon( KIcon( "go-previous" ) ); backwardAction->setEnabled( false ); backwardAction->setText( i18n( "Back" ) ); d->backwardIcon = addLeftHeaderAction( backwardAction ); connect( d->backwardIcon, SIGNAL(clicked()), this, SLOT(_goBackward()) ); QAction* forwardAction = new QAction( this ); forwardAction->setIcon( KIcon( "go-next" ) ); forwardAction->setEnabled( false ); forwardAction->setText( i18n( "Forward" ) ); d->forwardIcon = addLeftHeaderAction( forwardAction ); connect( d->forwardIcon, SIGNAL(clicked()), this, SLOT(_goForward()) ); QAction* reloadAction = new QAction( this ); reloadAction->setIcon( KIcon( "view-refresh" ) ); reloadAction->setText( i18n( "Reload" ) ); d->reloadIcon = addLeftHeaderAction( reloadAction ); connect( d->reloadIcon, SIGNAL(clicked()), this, SLOT(_reloadWikipedia()) ); QAction* artistAction = new QAction( this ); artistAction->setIcon( KIcon( "filename-artist-amarok" ) ); artistAction->setText( i18n( "Artist" ) ); d->artistIcon = addRightHeaderAction( artistAction ); connect( d->artistIcon, SIGNAL(clicked()), this, SLOT(_gotoArtist()) ); QAction* composerAction = new QAction( this ); composerAction->setIcon( KIcon( "filename-composer-amarok" ) ); composerAction->setText( i18n( "Composer" ) ); d->composerIcon = addRightHeaderAction( composerAction ); connect( d->composerIcon, SIGNAL(clicked()), this, SLOT(_gotoComposer()) ); QAction* albumAction = new QAction( this ); albumAction->setIcon( KIcon( "filename-album-amarok" ) ); albumAction->setText( i18n( "Album" ) ); d->albumIcon = addRightHeaderAction( albumAction ); connect( d->albumIcon, SIGNAL(clicked()), this, SLOT(_gotoAlbum()) ); QAction* trackAction = new QAction( this ); trackAction->setIcon( KIcon( "filename-title-amarok" ) ); trackAction->setText( i18n( "Track" ) ); d->trackIcon = addRightHeaderAction( trackAction ); connect( d->trackIcon, SIGNAL(clicked()), this, SLOT(_gotoTrack()) ); QAction* settingsAction = new QAction( this ); settingsAction->setIcon( KIcon( "preferences-system" ) ); settingsAction->setText( i18n( "Settings" ) ); d->settingsIcon = addRightHeaderAction( settingsAction ); connect( d->settingsIcon, SIGNAL(clicked()), this, SLOT(showConfigurationInterface()) ); QGraphicsLinearLayout *layout = new QGraphicsLinearLayout( Qt::Vertical ); layout->setSpacing( 2 ); layout->addItem( m_header ); layout->addItem( d->webView ); setLayout( layout ); dataEngine( "amarok-wikipedia" )->connectSource( "wikipedia", this ); d->dataContainer = dataEngine( "amarok-wikipedia" )->containerForSource( "wikipedia" ); // Read config and inform the engine. d->langList = Amarok::config("Wikipedia Applet").readEntry( "PreferredLang", QStringList() << "en" ); d->useMobileWikipedia = Amarok::config("Wikipedia Applet").readEntry( "UseMobile", false ); d->useSSL = Amarok::config( "Wikipedia Applet" ).readEntry( "UseSSL", true ); d->_paletteChanged( App::instance()->palette() ); d->dataContainer->setData( "lang", d->langList ); d->dataContainer->setData( "mobile", d->useMobileWikipedia ); d->dataContainer->setData( "ssl", d->useSSL ); d->scheduleEngineUpdate(); updateConstraints(); } void WikipediaApplet::constraintsEvent( Plasma::Constraints constraints ) { Context::Applet::constraintsEvent( constraints ); update(); } bool WikipediaApplet::hasHeightForWidth() const { return true; } qreal WikipediaApplet::heightForWidth( qreal width ) const { Q_D( const WikipediaApplet ); return width * d->aspectRatio; } void WikipediaApplet::dataUpdated( const QString &source, const Plasma::DataEngine::Data &data ) { DEBUG_BLOCK Q_UNUSED( source ) Q_D( WikipediaApplet ); if( data.isEmpty() ) { debug() << "data Empty!"; d->webView->hide(); setCollapseOn(); return; } if( data.contains( "stopped" ) ) { debug() << "stopped"; d->dataContainer->removeAllData(); if( d->webView->title().isEmpty() ) { d->webView->hide(); setCollapseOn(); } return; } if( data.contains( "busy" ) ) { if( canAnimate() && data["busy"].toBool() ) setBusy( true ); return; } else { d->webView->show(); setBusy( false ); } if( data.contains( "message" ) ) { setCollapseOn(); // messages have higher priority than pages const QString &message = data.value( "message" ).toString(); if( !message.isEmpty() ) { setHeaderText( i18n( "Wikipedia: %1", message ) ); d->dataContainer->removeAllData(); } } else if( data.contains( "sourceUrl" ) ) { - const KUrl &url = data.value( "sourceUrl" ).value<KUrl>(); + const QUrl &url = data.value( "sourceUrl" ).value<QUrl>(); d->setUrl( url ); debug() << "source URL" << url; setCollapseOff(); } else if( data.contains( "page" ) ) { if( data.contains( "url" ) && !data.value( "url" ).toUrl().isEmpty() ) { const QUrl &url = data.value( "url" ).toUrl(); d->_updateWebFonts(); d->currentUrl = url; d->webView->setHtml( data[ "page" ].toString(), url ); d->dataContainer->removeAllData(); } setCollapseOff(); } else { setHeaderText( i18n( "Wikipedia" ) ); } } void WikipediaApplet::loadWikipediaUrl( const QString &url ) { Q_D( WikipediaApplet ); d->_linkClicked( QUrl(url) ); } void WikipediaApplet::createConfigurationInterface( KConfigDialog *parent ) { Q_D( WikipediaApplet ); parent->setButtons( KDialog::Ok | KDialog::Cancel ); KConfigGroup configuration = config(); QWidget *langSettings = new QWidget; d->languageSettingsUi.setupUi( langSettings ); d->languageSettingsUi.downloadButton->setGuiItem( KStandardGuiItem::find() ); d->languageSettingsUi.langSelector->availableListWidget()->setAlternatingRowColors( true ); d->languageSettingsUi.langSelector->selectedListWidget()->setAlternatingRowColors( true ); d->languageSettingsUi.langSelector->availableListWidget()->setUniformItemSizes( true ); d->languageSettingsUi.langSelector->selectedListWidget()->setUniformItemSizes( true ); d->languageSettingsUi.progressBar->setEnabled( false ); QWidget *genSettings = new QWidget; d->generalSettingsUi.setupUi( genSettings ); d->generalSettingsUi.mobileCheckBox->setCheckState( d->useMobileWikipedia ? Qt::Checked : Qt::Unchecked ); d->generalSettingsUi.sslCheckBox->setCheckState( d->useSSL ? Qt::Checked : Qt::Unchecked ); connect( d->languageSettingsUi.downloadButton, SIGNAL(clicked()), this, SLOT(_getLangMap()) ); connect( parent, SIGNAL(okClicked()), this, SLOT(_loadSettings()) ); parent->addPage( genSettings, i18n( "Wikipedia General Settings" ), "configure" ); parent->addPage( langSettings, i18n( "Wikipedia Language Settings" ), "applications-education-language" ); QTimer::singleShot( 0, this, SLOT(_configureLangSelector()) ); } diff --git a/src/context/applets/wikipedia/WikipediaApplet.h b/src/context/applets/wikipedia/WikipediaApplet.h index 9663ae309e..592d5259a0 100644 --- a/src/context/applets/wikipedia/WikipediaApplet.h +++ b/src/context/applets/wikipedia/WikipediaApplet.h @@ -1,91 +1,91 @@ /**************************************************************************************** * Copyright (c) 2007 Leo Franchi <lfranchi@gmail.com> * * Copyright (c) 2009 Simon Esneault <simon.esneault@gmail.com> * * * * 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, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ #ifndef WIKIPEDIA_APPLET_H #define WIKIPEDIA_APPLET_H #include "context/Applet.h" #include "context/DataEngine.h" #include "NetworkAccessManagerProxy.h" class QAction; class KDialog; class KConfigDialog; class QListWidgetItem; class WikipediaAppletPrivate; namespace Plasma { class WebView; class IconWidget; } class WikipediaApplet : public Context::Applet { Q_OBJECT public: WikipediaApplet( QObject* parent, const QVariantList& args ); ~WikipediaApplet(); void constraintsEvent( Plasma::Constraints constraints = Plasma::AllConstraints ); bool hasHeightForWidth() const; qreal heightForWidth( qreal width ) const; public slots: virtual void init(); void dataUpdated( const QString& name, const Plasma::DataEngine::Data& data ); void loadWikipediaUrl( const QString &url ); protected: void createConfigurationInterface(KConfigDialog *parent); private: WikipediaAppletPrivate *const d_ptr; Q_DECLARE_PRIVATE( WikipediaApplet ) Q_PRIVATE_SLOT( d_ptr, void _goBackward() ) Q_PRIVATE_SLOT( d_ptr, void _goForward() ) Q_PRIVATE_SLOT( d_ptr, void _gotoAlbum() ) Q_PRIVATE_SLOT( d_ptr, void _gotoArtist() ) Q_PRIVATE_SLOT( d_ptr, void _gotoComposer() ) Q_PRIVATE_SLOT( d_ptr, void _gotoTrack() ) Q_PRIVATE_SLOT( d_ptr, void _linkClicked(const QUrl&) ) Q_PRIVATE_SLOT( d_ptr, void _loadSettings() ) Q_PRIVATE_SLOT( d_ptr, void _paletteChanged(const QPalette&) ) Q_PRIVATE_SLOT( d_ptr, void _reloadWikipedia() ) Q_PRIVATE_SLOT( d_ptr, void _updateWebFonts() ) Q_PRIVATE_SLOT( d_ptr, void _getLangMapProgress(qint64,qint64) ) - Q_PRIVATE_SLOT( d_ptr, void _getLangMapFinished(const KUrl&,QByteArray,NetworkAccessManagerProxy::Error) ) + Q_PRIVATE_SLOT( d_ptr, void _getLangMapFinished(const QUrl&,QByteArray,NetworkAccessManagerProxy::Error) ) Q_PRIVATE_SLOT( d_ptr, void _getLangMap() ) Q_PRIVATE_SLOT( d_ptr, void _configureLangSelector() ) Q_PRIVATE_SLOT( d_ptr, void _langSelectorItemChanged(QListWidgetItem*) ) Q_PRIVATE_SLOT( d_ptr, void _titleChanged(const QString&) ) Q_PRIVATE_SLOT( d_ptr, void _pageLoadStarted() ) Q_PRIVATE_SLOT( d_ptr, void _pageLoadProgress(int) ) Q_PRIVATE_SLOT( d_ptr, void _pageLoadFinished(bool) ) Q_PRIVATE_SLOT( d_ptr, void _searchLineEditTextEdited(const QString&) ) Q_PRIVATE_SLOT( d_ptr, void _searchLineEditReturnPressed() ) Q_PRIVATE_SLOT( d_ptr, void _jsWindowObjectCleared() ) }; AMAROK_EXPORT_APPLET( wikipedia, WikipediaApplet ) #endif diff --git a/src/context/applets/wikipedia/WikipediaApplet_p.h b/src/context/applets/wikipedia/WikipediaApplet_p.h index df5ddc336e..7288f60065 100644 --- a/src/context/applets/wikipedia/WikipediaApplet_p.h +++ b/src/context/applets/wikipedia/WikipediaApplet_p.h @@ -1,288 +1,288 @@ /**************************************************************************************** * Copyright (c) 2007 Leo Franchi <lfranchi@gmail.com> * * Copyright (c) 2009 Simon Esneault <simon.esneault@gmail.com> * * Copyright (c) 2010 Rick W. Chen <stuffcorpse@archlinux.us> * * * * 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, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ #ifndef AMAROK_WIKIPEDIAAPPLET_P_H #define AMAROK_WIKIPEDIAAPPLET_P_H #include "WikipediaApplet.h" #include "ui_wikipediaGeneralSettings.h" #include "ui_wikipediaLanguageSettings.h" #include <KGraphicsWebView> #include <KLineEdit> -#include <KUrl> +#include <QUrl> #include <Plasma/LineEdit> #include <Plasma/Svg> #include <Plasma/SvgWidget> #include <QGraphicsSceneResizeEvent> #include <QStack> #include <QWebFrame> #include <QWebPage> #include <QWebInspector> #include <QWebSettings> class KTemporaryFile; class WikipediaWebView; namespace Plasma { class DataContainer; class IconWidget; } class WikipediaAppletPrivate { private: WikipediaApplet *const q_ptr; Q_DECLARE_PUBLIC( WikipediaApplet ) public: WikipediaAppletPrivate( WikipediaApplet *parent ) : q_ptr( parent ) , css( 0 ) , dataContainer( 0 ) , albumIcon( 0 ) , artistIcon( 0 ) , composerIcon( 0 ) , backwardIcon( 0 ) , forwardIcon( 0 ) , reloadIcon( 0 ) , settingsIcon( 0 ) , trackIcon( 0 ) , webView( 0 ) , proxyWidget( 0 ) , aspectRatio( 0 ) , isForwardHistory( false ) , isBackwardHistory( false ) {} ~WikipediaAppletPrivate() {} // functions void pushUrlHistory( const QUrl &url ); void parseWikiLangXml( const QByteArray &xml ); qint64 writeStyleSheet( const QByteArray &css ); void scheduleEngineUpdate(); void setUrl( const QUrl &url ); void updateNavigationIcons(); // private slots void _linkClicked( const QUrl &url ); void _loadSettings(); void _goBackward(); void _goForward(); void _gotoArtist(); void _gotoComposer(); void _gotoAlbum(); void _gotoTrack(); void _switchToLang( const QString &lang ); void _reloadWikipedia(); void _updateWebFonts(); void _paletteChanged( const QPalette &palette ); void _getLangMapProgress( qint64 received, qint64 total ); - void _getLangMapFinished( const KUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ); + void _getLangMapFinished( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ); void _getLangMap(); void _configureLangSelector(); void _langSelectorItemChanged( QListWidgetItem *item ); void _titleChanged( const QString &title ); void _pageLoadStarted(); void _pageLoadProgress( int progress ); void _pageLoadFinished( bool ok ); void _searchLineEditTextEdited( const QString &text ); void _searchLineEditReturnPressed(); void _jsWindowObjectCleared(); // data members enum WikiLangRoles { PrefixRole = Qt::UserRole + 1, UrlPrefixRole = Qt::UserRole + 2, LanguageStringRole = Qt::UserRole + 3 }; KTemporaryFile *css; Plasma::DataContainer *dataContainer; Plasma::IconWidget *albumIcon; Plasma::IconWidget *artistIcon; Plasma::IconWidget *composerIcon; Plasma::IconWidget *backwardIcon; Plasma::IconWidget *forwardIcon; Plasma::IconWidget *reloadIcon; Plasma::IconWidget *settingsIcon; Plasma::IconWidget *trackIcon; WikipediaWebView *webView; QGraphicsProxyWidget *proxyWidget; QStack<QUrl> historyBack; QStack<QUrl> historyForward; QUrl currentUrl; QStringList langList; Ui::wikipediaGeneralSettings generalSettingsUi; Ui::wikipediaLanguageSettings languageSettingsUi; qreal aspectRatio; bool isForwardHistory; bool isBackwardHistory; bool useMobileWikipedia; bool useSSL; }; class WikipediaSearchLineEdit : public Plasma::LineEdit { Q_OBJECT public: WikipediaSearchLineEdit( QGraphicsWidget *parent = 0 ) : Plasma::LineEdit( parent ) {} ~WikipediaSearchLineEdit() {} void setFocus( Qt::FocusReason reason ) { nativeWidget()->setFocus( reason ); } protected: void focusOutEvent( QFocusEvent *event ) { hide(); nativeWidget()->clear(); parentWidget()->setFocus(); event->accept(); } void keyPressEvent( QKeyEvent *event ) { if( event->key() == Qt::Key_Escape ) { clearFocus(); event->accept(); } else Plasma::LineEdit::keyPressEvent( event ); } }; class WikipediaWebView : public KGraphicsWebView { Q_OBJECT public: WikipediaWebView( QGraphicsItem *parent = 0 ) : KGraphicsWebView( parent ) { m_lineEdit = new WikipediaSearchLineEdit( this ); m_lineEdit->setContentsMargins( 0, 0, 0, 0 ); m_lineEdit->setClearButtonShown( true ); m_lineEdit->setVisible( false ); Plasma::Svg *borderSvg = new Plasma::Svg( this ); borderSvg->setImagePath( "widgets/scrollwidget" ); m_topBorder = new Plasma::SvgWidget( this ); m_topBorder->setSvg( borderSvg ); m_topBorder->setElementID( "border-top" ); m_topBorder->setZValue( 900 ); m_topBorder->resize( -1, 10.0 ); m_topBorder->show(); m_bottomBorder = new Plasma::SvgWidget( this ); m_bottomBorder->setSvg( borderSvg ); m_bottomBorder->setElementID( "border-bottom" ); m_bottomBorder->setZValue( 900 ); m_bottomBorder->resize( -1, 10.0 ); m_bottomBorder->show(); page()->parent()->installEventFilter( this ); #if defined(DEBUG_BUILD_TYPE) page()->settings()->setAttribute( QWebSettings::DeveloperExtrasEnabled, true ); m_inspector = new QWebInspector; m_inspector->setPage( page() ); #endif } ~WikipediaWebView() { #if defined(DEBUG_BUILD_TYPE) delete m_inspector; #endif } Plasma::LineEdit *lineEdit() { return m_lineEdit; } protected: bool eventFilter( QObject *obj , QEvent *event ) { if( obj == page()->parent() ) { if( event->type() == QEvent::KeyPress || event->type() == QEvent::ShortcutOverride ) { QKeyEvent *keyEvent = static_cast<QKeyEvent*>( event ); keyPressEvent( keyEvent ); return true; } return false; } return KGraphicsWebView::eventFilter( obj, event ); } void keyPressEvent( QKeyEvent *event ) { if( event->key() == Qt::Key_Slash || event->matches( QKeySequence::Find ) ) { qreal offsetX = m_lineEdit->rect().width(); qreal offsetY = m_lineEdit->rect().height(); offsetX += page()->currentFrame()->scrollBarGeometry( Qt::Vertical ).width(); m_lineEdit->setPos( rect().bottomRight() - QPointF( offsetX, offsetY ) ); m_lineEdit->setFocus( Qt::PopupFocusReason ); m_lineEdit->show(); event->accept(); } else KGraphicsWebView::keyPressEvent( event ); } void resizeEvent( QGraphicsSceneResizeEvent *event ) { KGraphicsWebView::resizeEvent( event ); if( m_topBorder ) { m_topBorder->resize( event->newSize().width(), m_topBorder->size().height() ); m_bottomBorder->resize( event->newSize().width(), m_bottomBorder->size().height() ); QPointF bottomPoint = boundingRect().bottomLeft(); bottomPoint.ry() -= m_bottomBorder->size().height(); m_bottomBorder->setPos( bottomPoint ); m_topBorder->setPos( mapFromParent( pos() ) ); } } private: #if defined(DEBUG_BUILD_TYPE) QWebInspector *m_inspector; #endif WikipediaSearchLineEdit *m_lineEdit; Plasma::SvgWidget *m_topBorder; Plasma::SvgWidget *m_bottomBorder; }; #endif /* AMAROK_WIKIPEDIAAPPLET_P_H */ diff --git a/src/context/engines/labels/LabelsEngine.cpp b/src/context/engines/labels/LabelsEngine.cpp index 0b850395a8..447051c540 100644 --- a/src/context/engines/labels/LabelsEngine.cpp +++ b/src/context/engines/labels/LabelsEngine.cpp @@ -1,373 +1,373 @@ /**************************************************************************************** * Copyright (c) 2009 Simon Esneault <simon.esneault@gmail.com> * * Copyright (c) 2010 Daniel Faust <hessijames@gmail.com> * * * * 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, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ #define DEBUG_PREFIX "LabelsEngine" #include "LabelsEngine.h" #include "EngineController.h" #include "context/ContextObserver.h" #include "context/ContextView.h" #include "core/collections/Collection.h" #include "core/collections/QueryMaker.h" #include "core/meta/Meta.h" #include "core/support/Debug.h" #include "core-impl/collections/support/CollectionManager.h" #include <KIO/Job> #include <KLocale> #include <QDomDocument> using namespace Context; LabelsEngine::LabelsEngine( QObject *parent, const QList<QVariant> &args ) : DataEngine( parent ) , ContextObserver( ContextView::self() ) { Q_UNUSED( args ) m_sources << "lastfm" ; m_timeoutTimer.setInterval( 10000 ); m_timeoutTimer.setSingleShot( true ); connect( &m_timeoutTimer, SIGNAL(timeout()), this, SLOT(timeout()) ); EngineController *engine = The::engineController(); connect( engine, SIGNAL(trackChanged(Meta::TrackPtr)), this, SLOT(update()) ); connect( engine, SIGNAL(trackMetadataChanged(Meta::TrackPtr)), this, SLOT(update()) ); } LabelsEngine::~LabelsEngine() { DEBUG_BLOCK } QStringList LabelsEngine::sources() const { return m_sources; } bool LabelsEngine::sourceRequestEvent( const QString &name ) { DEBUG_BLOCK Collections::Collection *coll = CollectionManager::instance()->primaryCollection(); if( coll ) { Collections::QueryMaker *qm = coll->queryMaker(); qm->setAutoDelete( true ); qm->setQueryType( Collections::QueryMaker::Label ); m_allLabels.clear(); connect( qm, SIGNAL(newResultReady(Meta::LabelList)), SLOT(resultReady(Meta::LabelList)), Qt::QueuedConnection ); connect( qm, SIGNAL(queryDone()), SLOT(dataQueryDone()) ); qm->run(); } update( name == "reload" ); return true; } void LabelsEngine::resultReady( const Meta::LabelList &labels ) { foreach( const Meta::LabelPtr &label, labels ) { if( !label->name().isEmpty() ) m_allLabels << label->name(); } } void LabelsEngine::dataQueryDone() { DEBUG_BLOCK QVariant varAll; varAll.setValue< QStringList >( m_allLabels ); setData( "labels", "all", varAll ); } void LabelsEngine::update( bool reload ) { Meta::TrackPtr track = The::engineController()->currentTrack(); if( !track ) { removeAllData( "labels" ); m_artist.clear(); m_title.clear(); m_album.clear(); m_userLabels.clear(); m_webLabels.clear(); setData( "labels", "state", "stopped" ); return; } const QString title = track->name(); Meta::ArtistPtr artist = track->artist(); if( !artist ) { setData( "labels", "message", i18n( "No labels found on Last.fm" ) ); debug() << "track has no artist, returning"; return; } QStringList userLabels; foreach( const Meta::LabelPtr &label, track->labels() ) userLabels += label->name(); userLabels.sort(); m_userLabels.sort(); // check what changed if( !reload && artist->name() == m_artist && title == m_title && userLabels == m_userLabels ) { // nothing important changed return; } else if( !reload && artist->name() == m_artist && title == m_title ) { // only the labels changed - no download necessary debug() << "only the labels changed - no download necessary"; QVariant varUser; varUser.setValue< QStringList >( userLabels ); setData( "labels", "user", varUser ); m_userLabels = userLabels; return; } removeAllData( "labels" ); setData( "labels", "state", "started" ); m_artist = artist->name(); m_title = title; if( track->album() ) m_album = track->album()->name(); else m_album.clear(); m_userLabels = userLabels; m_webLabels.clear(); QVariant varUser; varUser.setValue< QStringList >( m_userLabels ); setData( "labels", "user", varUser ); m_try = 0; fetchLastFm(); } void LabelsEngine::fetchLastFm() { QStringList separators; QString currentArtist; QString currentTitle; if( m_title.isEmpty() || m_artist.isEmpty() ) { // stop timeout timer m_timeoutTimer.stop(); setData( "labels", "message", i18n( "No labels found on Last.fm" ) ); debug() << "current track is invalid, returning"; return; } if( m_try == 0 ) { currentArtist = m_artist; currentTitle = m_title; m_timeoutTimer.start(); } else if( m_try == 1 ) { currentArtist = m_artist; currentTitle = m_title; separators.clear(); separators << " (" << " [" << " - " << " featuring " << " feat. " << " feat " << " ft. " << " ft " << "/"; foreach( const QString &separator, separators ) { if( m_title.contains(separator,Qt::CaseInsensitive) ) { currentTitle = m_title.left( m_title.indexOf(separator,0,Qt::CaseInsensitive) ); break; } } if ( currentTitle == m_title ) { debug() << "try 2: title is the same, retrying"; m_try++; fetchLastFm(); return; } } else if( m_try == 2 ) { currentArtist = m_artist; currentTitle = m_title; separators.clear(); separators << " vs. " << " vs " << " featuring " << " feat. " << " feat " << " ft. " << " ft " << ", " << " and " << " & " << "/"; foreach( const QString &separator, separators ) { if( m_artist.contains(separator,Qt::CaseInsensitive) ) { currentArtist = m_artist.left( m_artist.indexOf(separator,0,Qt::CaseInsensitive) ); break; } } separators.clear(); separators << " (" << " [" << " - " << " featuring " << " feat. " << " feat " << " ft. " << " ft " << "/"; foreach( const QString &separator, separators ) { if( m_title.contains(separator,Qt::CaseInsensitive) ) { currentTitle = m_title.left( m_title.indexOf(separator,0,Qt::CaseInsensitive) ); break; } } if( currentArtist == m_artist ) // the title got modified the same way as on the last try { // stop timeout timer m_timeoutTimer.stop(); setData( "labels", "message", i18n( "No labels found on Last.fm" ) ); debug() << "try 3: artist and title are the same, returning"; return; } } else { // shouldn't happen // stop timeout timer m_timeoutTimer.stop(); setData( "labels", "message", i18n( "No labels found on Last.fm" ) ); debug() << "try > 2, returning"; return; } if( !currentArtist.isEmpty() && !currentTitle.isEmpty() ) { setData( "labels", "message", "fetching"); // send the atist and title actually used for searching labels setData( "labels", "artist", currentArtist ); setData( "labels", "title", currentTitle ); setData( "labels", "album", m_album ); // Query lastfm - KUrl lastFmUrl; + QUrl lastFmUrl; lastFmUrl.setScheme( "http" ); lastFmUrl.setHost( "ws.audioscrobbler.com" ); lastFmUrl.setPath( "/2.0/" ); lastFmUrl.addQueryItem( "method", "track.gettoptags" ); lastFmUrl.addQueryItem( "api_key", "402d3ca8e9bc9d3cf9b85e1202944ca5" ); lastFmUrl.addQueryItem( "artist", currentArtist.toLocal8Bit() ); lastFmUrl.addQueryItem( "track", currentTitle.toLocal8Bit() ); m_lastFmUrl = lastFmUrl; QNetworkRequest req( lastFmUrl ); // req.setAttribute( QNetworkRequest::ConnectionEncryptedAttribute, QNetworkRequest::AlwaysNetwork ); The::networkAccessManager()->get( req ); The::networkAccessManager()->getData( lastFmUrl, this, - SLOT(resultLastFm(KUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); + SLOT(resultLastFm(QUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); } else { // stop timeout timer m_timeoutTimer.stop(); setData( "labels", "message", i18n( "No labels found on Last.fm" ) ); debug() << "artist or track empty"; } } -void LabelsEngine::resultLastFm( const KUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ) +void LabelsEngine::resultLastFm( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ) { DEBUG_BLOCK; if( m_lastFmUrl != url ) { debug() << "urls not matching, returning"; return; } if( e.code != QNetworkReply::NoError ) { // stop timeout timer m_timeoutTimer.stop(); setData( "labels", "message", i18n( "Unable to retrieve from Last.fm" ) ); debug() << "Unable to retrieve last.fm information: " << e.description; return; } QDomDocument xmlDoc; xmlDoc.setContent( data ); const QDomElement topElement = xmlDoc.elementsByTagName("toptags").at(0).toElement(); const QDomNodeList xmlNodeList = topElement.elementsByTagName( "tag" ); for( uint i = 0; i < xmlNodeList.length(); i++ ) { // Get all the information const QDomElement nd = xmlNodeList.at( i ).toElement(); const QDomElement nameElement = nd.elementsByTagName("name").at(0).toElement(); const QString name = nameElement.text().toLower(); const QDomElement countElement = nd.elementsByTagName("count").at(0).toElement(); const int count = countElement.text().toInt(); m_webLabels.insert( name, count ); } if( m_webLabels.isEmpty() ) { if( m_try < 2 ) { m_try++; fetchLastFm(); } else { // stop timeout timer m_timeoutTimer.stop(); setData( "labels", "message", i18n( "No labels found on Last.fm" ) ); } } else { // remove previous message removeData( "labels", "message" ); // stop timeout timer m_timeoutTimer.stop(); QVariant varWeb; varWeb.setValue< QMap< QString, QVariant > > ( m_webLabels ); setData( "labels", "web", varWeb ); } } void LabelsEngine::timeout() { setData( "labels", "message", i18n( "No connection to Last.fm" ) ); } diff --git a/src/context/engines/labels/LabelsEngine.h b/src/context/engines/labels/LabelsEngine.h index c2c23f61ce..28569d8e65 100644 --- a/src/context/engines/labels/LabelsEngine.h +++ b/src/context/engines/labels/LabelsEngine.h @@ -1,98 +1,98 @@ /**************************************************************************************** * Copyright (c) 2009 Simon Esneault <simon.esneault@gmail.com> * * Copyright (c) 2010 Daniel Faust <hessijames@gmail.com> * * * * 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, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ #ifndef AMAROK_LABELS_ENGINE #define AMAROK_LABELS_ENGINE #include "ContextObserver.h" #include "context/DataEngine.h" #include "core/meta/forward_declarations.h" #include "network/NetworkAccessManagerProxy.h" #include <QMap> #include <QTimer> #include <QWeakPointer> using namespace Context; /** * This class provide labels from last.fm * */ class LabelsEngine : public DataEngine, public ContextObserver { Q_OBJECT public: LabelsEngine( QObject *parent, const QList<QVariant> &args ); virtual ~LabelsEngine(); QStringList sources() const; protected: // reimplemented from Plasma::DataEngine bool sourceRequestEvent( const QString &name ); private slots: void update( bool reload = false ); /** * This slots will handle last.fm result for this query: * API key is : 402d3ca8e9bc9d3cf9b85e1202944ca5 * http://ws.audioscrobbler.com/2.0/?method=track.gettoptags&artist=radiohead&track=paranoid+android&api_key=b25b959554ed76058ac220b7b2e0a026 * see here for details: http://www.lastfm.com/api/ */ - void resultLastFm( const KUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ); + void resultLastFm( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ); void resultReady( const Meta::LabelList &labels ); void dataQueryDone(); void timeout(); private: /** * Engine was updated, so we check if the songs is different, and if it is, we delete every and start * all the query/ fetching stuff */ void fetchLastFm(); void updateLocal(); QTimer m_timeoutTimer; /// The URL for the network request - KUrl m_lastFmUrl; + QUrl m_lastFmUrl; QStringList m_sources; // Cache the artist and title of the current track so we can check against metadata // updates. We only want to update the labels if the artist change QString m_artist; QString m_title; // Send the album name to the applet, used to filter labels that match the album QString m_album; int m_try; QStringList m_allLabels; // all labels known to amarok QStringList m_userLabels; // user labels QMap < QString, QVariant > m_webLabels; // downloaded labels }; AMAROK_EXPORT_DATAENGINE( labels, LabelsEngine ) #endif diff --git a/src/context/engines/lyrics/LyricsEngine.cpp b/src/context/engines/lyrics/LyricsEngine.cpp index 3cc69e8fc3..6f8efcec03 100644 --- a/src/context/engines/lyrics/LyricsEngine.cpp +++ b/src/context/engines/lyrics/LyricsEngine.cpp @@ -1,194 +1,194 @@ /**************************************************************************************** * Copyright (c) 2007-2008 Leo Franchi <lfranchi@gmail.com> * * Copyright (c) 2008 Mark Kretschmann <kretschmann@kde.org> * * * * 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, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ #define DEBUG_PREFIX "LyricsEngine" #include "LyricsEngine.h" #include "EngineController.h" #include "scripting/scriptmanager/ScriptManager.h" #include "context/ContextView.h" #include "core/meta/Meta.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include <QTimer> #include <QTextDocument> using namespace Context; LyricsEngine::LyricsEngine( QObject* parent, const QList<QVariant>& /*args*/ ) : DataEngine( parent ) , LyricsObserver( LyricsManager::self() ) , m_isUpdateInProgress( false ) { EngineController* engine = The::engineController(); connect( engine, SIGNAL(trackChanged(Meta::TrackPtr)), this, SLOT(update()), Qt::QueuedConnection ); connect( engine, SIGNAL(trackMetadataChanged(Meta::TrackPtr)), this, SLOT(onTrackMetadataChanged(Meta::TrackPtr)), Qt::QueuedConnection ); } QStringList LyricsEngine::sources() const { QStringList sourcesList; sourcesList << "lyrics" << "suggested"; return sourcesList; } bool LyricsEngine::sourceRequestEvent( const QString& name ) { removeAllData( name ); setData( name, QVariant()); // in the case where we are resuming playback on startup. Need to be sure // the script manager is running and a lyrics script is loaded first. QTimer::singleShot( 0, this, SLOT(update()) ); return true; } void LyricsEngine::onTrackMetadataChanged( Meta::TrackPtr track ) { DEBUG_BLOCK // Only update if the lyrics have changed. QString artist = track->artist() ? track->artist()->name() : QString(); if( m_prevLyrics.artist != artist || m_prevLyrics.title != track->name() || m_prevLyrics.text != track->cachedLyrics() ) update(); } void LyricsEngine::update() { if( m_isUpdateInProgress ) return; m_isUpdateInProgress = true; // -- get current title and artist Meta::TrackPtr currentTrack = The::engineController()->currentTrack(); if( !currentTrack ) { debug() << "no current track"; m_prevLyrics.clear(); removeAllData( "lyrics" ); setData( "lyrics", "stopped", "stopped" ); m_isUpdateInProgress = false; return; } QString title = currentTrack->name(); QString artist = currentTrack->artist() ? currentTrack->artist()->name() : QString(); // -- clean up title const QString magnatunePreviewString = QLatin1String( "PREVIEW: buy it at www.magnatune.com" ); if( title.contains(magnatunePreviewString, Qt::CaseSensitive) ) title = title.remove( " (" + magnatunePreviewString + ')' ); if( artist.contains(magnatunePreviewString, Qt::CaseSensitive) ) artist = artist.remove( " (" + magnatunePreviewString + ')' ); if( title.isEmpty() && currentTrack ) { /* If title is empty, try to use pretty title. The fact that it often (but not always) has "artist name" together, can be bad, but at least the user will hopefully get nice suggestions. */ QString prettyTitle = currentTrack->prettyName(); int h = prettyTitle.indexOf( QLatin1Char('-') ); if ( h != -1 ) { title = prettyTitle.mid( h + 1 ).trimmed(); if( title.contains(magnatunePreviewString, Qt::CaseSensitive) ) title = title.remove( " (" + magnatunePreviewString + ')' ); if( artist.isEmpty() ) { artist = prettyTitle.mid( 0, h ).trimmed(); if( artist.contains(magnatunePreviewString, Qt::CaseSensitive) ) artist = artist.remove( " (" + magnatunePreviewString + ')' ); } } } - LyricsData lyrics = { currentTrack->cachedLyrics(), title, artist, KUrl() }; + LyricsData lyrics = { currentTrack->cachedLyrics(), title, artist, QUrl() }; // Check if the title, the artist and the lyrics are still the same. if( !lyrics.text.isEmpty() && (lyrics.text == m_prevLyrics.text) ) { debug() << "nothing changed:" << lyrics.title; newLyrics( lyrics ); m_isUpdateInProgress = false; return; } // don't rely on caching for streams const bool cached = !LyricsManager::self()->isEmpty( lyrics.text ) && !The::engineController()->isStream(); if( cached ) { newLyrics( lyrics ); } else { // no lyrics, and no lyrics script! if( !ScriptManager::instance()->lyricsScriptRunning() ) { debug() << "no lyrics script running"; removeAllData( "lyrics" ); setData( "lyrics", "noscriptrunning", "noscriptrunning" ); disconnect( ScriptManager::instance(), SIGNAL(lyricsScriptStarted()), this, 0 ); connect( ScriptManager::instance(), SIGNAL(lyricsScriptStarted()), SLOT(update()) ); m_isUpdateInProgress = false; return; } // fetch by lyrics script removeAllData( "lyrics" ); setData( "lyrics", "fetching", "fetching" ); ScriptManager::instance()->notifyFetchLyrics( lyrics.artist, lyrics.title, "", currentTrack ); } m_isUpdateInProgress = false; } void LyricsEngine::newLyrics( const LyricsData &lyrics ) { QString key = Qt::mightBeRichText( lyrics.text ) ? QLatin1String( "html" ) : QLatin1String( "lyrics" ); removeAllData( "lyrics" ); setData( "lyrics", key, QVariant::fromValue(lyrics) ); m_prevLyrics = lyrics; } void LyricsEngine::newSuggestions( const QVariantList &suggested ) { DEBUG_BLOCK // each string is in "title - artist <url>" form removeAllData( "lyrics" ); setData( "lyrics", "suggested", suggested ); } void LyricsEngine::lyricsMessage( const QString& key, const QString &val ) { DEBUG_BLOCK removeAllData( "lyrics" ); setData( "lyrics", key, val ); } diff --git a/src/context/engines/photos/PhotosEngine.cpp b/src/context/engines/photos/PhotosEngine.cpp index 3070b7c0ce..e974a709c5 100644 --- a/src/context/engines/photos/PhotosEngine.cpp +++ b/src/context/engines/photos/PhotosEngine.cpp @@ -1,262 +1,262 @@ /**************************************************************************************** * Copyright (c) 2009 Simon Esneault <simon.esneault@gmail.com> * * * * 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, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ #define DEBUG_PREFIX "PhotosEngine" #include "PhotosEngine.h" #include "EngineController.h" #include "context/ContextView.h" #include "core/meta/Meta.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include <QXmlStreamReader> #include <QPixmap> using namespace Context; PhotosEngine::PhotosEngine( QObject* parent, const QList<QVariant>& /*args*/ ) : DataEngine( parent ) , m_nbPhotos( 10 ) { m_sources << "flickr" ; } PhotosEngine::~PhotosEngine() { } void PhotosEngine::init() { DEBUG_BLOCK EngineController *controller = The::engineController(); connect( controller, SIGNAL(trackMetadataChanged(Meta::TrackPtr)), SLOT(trackChanged(Meta::TrackPtr)) ); connect( controller, SIGNAL(trackChanged(Meta::TrackPtr)), SLOT(trackChanged(Meta::TrackPtr)) ); connect( controller, SIGNAL(stopped(qint64,qint64)), SLOT(stopped()) ); } void PhotosEngine::stopped() { DEBUG_BLOCK removeAllData( "photos" ); setData( "photos", "message", "stopped" ); m_artist.clear(); m_currentTrack.clear(); } void PhotosEngine::trackChanged( Meta::TrackPtr track ) { if( !track ) return; update(); } QStringList PhotosEngine::sources() const { return m_sources; } int PhotosEngine::fetchSize() const { return m_nbPhotos; } void PhotosEngine::setFetchSize( int size ) { m_nbPhotos = size; } QStringList PhotosEngine::keywords() const { return m_keywords; } void PhotosEngine::setKeywords( const QStringList &keywords ) { m_keywords = keywords; } bool PhotosEngine::sourceRequestEvent( const QString& name ) { DEBUG_BLOCK bool force( false ); QStringList tokens = name.split( QLatin1Char(':'), QString::SkipEmptyParts ); if( tokens.contains( QLatin1String("forceUpdate") ) ) force = true; update( force ); return true; } void PhotosEngine::metadataChanged( Meta::TrackPtr track ) { const bool hasChanged = !track->artist() || track->artist()->name() != m_artist; if ( hasChanged ) update(); } void PhotosEngine::update( bool force ) { QString tmpYoutStr; // prevent Meta::TrackPtr currentTrack = The::engineController()->currentTrack(); if( !currentTrack || !currentTrack->artist() ) { debug() << "invalid current track"; setData( "photos", Plasma::DataEngine::Data() ); return; } else if( !force && currentTrack->artist()->name() == m_artist ) { debug() << "artist name unchanged"; setData( "photos", Plasma::DataEngine::Data() ); return; } else { unsubscribeFrom( m_currentTrack ); m_currentTrack = currentTrack; subscribeTo( currentTrack ); if ( !currentTrack ) return; // Save artist m_artist = currentTrack->artist()->name(); removeAllData( "photos" ); // Show the information if( !m_artist.isEmpty() ) { setData( "photos", "message", "Fetching"); setData( "photos", "artist", m_artist ); } else { removeAllData( "photos" ); return; } QStringList tags = m_keywords; tags << m_artist; tags.removeDuplicates(); // Query flickr, order by relevance, 10 max // Flickr :http://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=9c5a288116c34c17ecee37877397fe31&text=ARTIST&per_page=20 - KUrl flickrUrl; + QUrl flickrUrl; flickrUrl.setScheme( "http" ); flickrUrl.setHost( "api.flickr.com" ); flickrUrl.setPath( "/services/rest/" ); flickrUrl.addQueryItem( "method", "flickr.photos.search" ); flickrUrl.addQueryItem( "api_key", Amarok::flickrApiKey() ); flickrUrl.addQueryItem( "per_page", QString::number( m_nbPhotos ) ); flickrUrl.addQueryItem( "sort", "date-posted-desc" ); flickrUrl.addQueryItem( "media", "photos" ); flickrUrl.addQueryItem( "content_type", QString::number(1) ); flickrUrl.addQueryItem( "text", tags.join(" ") ); debug() << "Flickr url:" << flickrUrl; m_flickrUrls << flickrUrl; The::networkAccessManager()->getData( flickrUrl, this, - SLOT(resultFlickr(KUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); + SLOT(resultFlickr(QUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); } } void -PhotosEngine::resultFlickr( const KUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ) +PhotosEngine::resultFlickr( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ) { if( !m_flickrUrls.contains( url ) ) return; DEBUG_BLOCK m_flickrUrls.remove( url ); if( e.code != QNetworkReply::NoError ) { setData( "photos", "message", i18n( "Unable to retrieve from Flickr.com: %1", e.description ) ); debug() << "Unable to retrieve Flickr information:" << e.description; return; } if( data.isNull() ) { debug() << "Got bad xml!"; return; } removeAllData( "photos" ); QXmlStreamReader xml( data ); PhotosInfo::List photosInfo = photosListFromXml( xml ); debug() << "got" << photosInfo.size() << "photo info"; setData( "photos", "artist", m_artist ); setData( "photos", "data", qVariantFromValue( photosInfo ) ); } PhotosInfo::List PhotosEngine::photosListFromXml( QXmlStreamReader &xml ) { PhotosInfo::List photoList; xml.readNextStartElement(); // rsp if( xml.attributes().value(QLatin1String("stat")) != QLatin1String("ok") ) return photoList; xml.readNextStartElement(); // photos while( xml.readNextStartElement() ) { if( xml.name() == QLatin1String("photo") ) { const QXmlStreamAttributes &attr = xml.attributes(); QStringRef id = attr.value( QLatin1String("id") ); QStringRef farm = attr.value( QLatin1String("farm") ); QStringRef owner = attr.value( QLatin1String("owner") ); QStringRef secret = attr.value( QLatin1String("secret") ); QStringRef server = attr.value( QLatin1String("server") ); QStringRef title = attr.value( QLatin1String("title") ); - KUrl photoUrl; + QUrl photoUrl; photoUrl.setScheme( "http" ); photoUrl.setHost( QString("farm%1.static.flickr.com").arg( farm.toString() ) ); photoUrl.setPath( QString("/%1/%2_%3.jpg").arg( server.toString(), id.toString(), secret.toString() ) ); - KUrl pageUrl; + QUrl pageUrl; pageUrl.setScheme( "http" ); pageUrl.setHost( QLatin1String("www.flickr.com") ); pageUrl.setPath( QString("/photos/%1/%2").arg( owner.toString(), id.toString() ) ); PhotosInfoPtr info( new PhotosInfo ); info->title = title.toString(); info->urlpage = pageUrl; info->urlphoto = photoUrl; photoList.append( info ); } xml.skipCurrentElement(); } return photoList; } diff --git a/src/context/engines/photos/PhotosEngine.h b/src/context/engines/photos/PhotosEngine.h index 55fa5912b1..8678661fb0 100644 --- a/src/context/engines/photos/PhotosEngine.h +++ b/src/context/engines/photos/PhotosEngine.h @@ -1,102 +1,102 @@ #/**************************************************************************************** * Copyright (c) 2009 Simon Esneault <simon.esneault@gmail.com> * * * * 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, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ #ifndef AMAROK_PHOTOS_ENGINE #define AMAROK_PHOTOS_ENGINE #include "context/DataEngine.h" #include "core/meta/Observer.h" #include "NetworkAccessManagerProxy.h" #include "PhotosInfo.h" #include <QXmlStreamReader> using namespace Context; /** * This class provide photos from flickr * */ class PhotosEngine : public DataEngine, public Meta::Observer { Q_OBJECT Q_PROPERTY( int fetchSize READ fetchSize WRITE setFetchSize ) Q_PROPERTY( QStringList keywords READ keywords WRITE setKeywords ) public: PhotosEngine( QObject* parent, const QList<QVariant>& args ); virtual ~PhotosEngine(); void init(); int fetchSize() const; void setFetchSize( int size ); QStringList keywords() const; void setKeywords( const QStringList &keywords ); QStringList sources() const; // reimplemented from Meta::Observer using Observer::metadataChanged; void metadataChanged( Meta::TrackPtr track ); protected: //reimplement from Plasma::DataEngine bool sourceRequestEvent( const QString& name ); private slots: /** * This slots will handle Flickr result for this query : * API key is : 9c5a288116c34c17ecee37877397fe31 * Secret is : cc25e5a9532ddc97 * http://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=9c5a288116c34c17ecee37877397fe31&text=My+Bloody+Valentine * see here for details: http://www.flickr.com/services/api/ */ - void resultFlickr( const KUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ); + void resultFlickr( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ); void stopped(); void trackChanged( Meta::TrackPtr track ); private: /** * Engine was updated, so we check if the songs is different, and if it is, we delete every and start * all the query/ fetching stuff */ void update( bool force = false ); PhotosInfo::List photosListFromXml( QXmlStreamReader &xml ); // TODO implement a reload void reloadPhotos(); int m_nbPhotos; - QSet<KUrl> m_flickrUrls; + QSet<QUrl> m_flickrUrls; QStringList m_sources; Meta::TrackPtr m_currentTrack; // Cache the artist of the current track so we can check against metadata // updates. We only want to update the photos if the artist change QString m_artist; QStringList m_keywords; }; AMAROK_EXPORT_DATAENGINE( photos, PhotosEngine ) #endif diff --git a/src/context/engines/photos/PhotosInfo.h b/src/context/engines/photos/PhotosInfo.h index 59d0021160..d1fa2e4613 100644 --- a/src/context/engines/photos/PhotosInfo.h +++ b/src/context/engines/photos/PhotosInfo.h @@ -1,65 +1,65 @@ /**************************************************************************************** * * Copyright (c) 2009 Simon Esneault <simon.esneault@gmail.com> * * * * 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, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ #ifndef AMAROK_PHOTOS_INFO #define AMAROK_PHOTOS_INFO -#include <KUrl> +#include <QUrl> #include <KSharedPtr> #include <QSharedData> #include <QPixmap> class PhotosInfo; typedef KSharedPtr<PhotosInfo> PhotosInfoPtr; //! Struct PhotosInfo, contain all the info vor a photos class PhotosInfo : public QSharedData { public: typedef QList<PhotosInfoPtr> List; PhotosInfo() { static bool metaTypeRegistered = false; if( !metaTypeRegistered ) { qRegisterMetaType<PhotosInfo>( "PhotosInfo" ); qRegisterMetaType<PhotosInfoPtr>( "PhotosInfoPtr" ); qRegisterMetaType<PhotosInfo::List>( "PhotosInfo::List" ); metaTypeRegistered = true; } } PhotosInfo( const PhotosInfo &other ) : QSharedData( other ) , title( other.title ) , urlphoto( other.urlphoto ) , urlpage( other.urlpage ) {} ~PhotosInfo() {} QString title; // Name of the phtos - KUrl urlphoto; // url of the photos, for the download - KUrl urlpage; // Url for the browser ( http://www.flickr.com/photos/wanderlustg/322285063/ ) + QUrl urlphoto; // url of the photos, for the download + QUrl urlpage; // Url for the browser ( http://www.flickr.com/photos/wanderlustg/322285063/ ) }; Q_DECLARE_METATYPE( PhotosInfo ) Q_DECLARE_METATYPE( PhotosInfoPtr ) Q_DECLARE_METATYPE( PhotosInfo::List ) #endif diff --git a/src/context/engines/similarartists/SimilarArtistsEngine.cpp b/src/context/engines/similarartists/SimilarArtistsEngine.cpp index 60c39861b6..98919d9243 100644 --- a/src/context/engines/similarartists/SimilarArtistsEngine.cpp +++ b/src/context/engines/similarartists/SimilarArtistsEngine.cpp @@ -1,176 +1,176 @@ /*************************************************************************************** * Copyright (c) 2009 Nathan Sala <sala.nathan@gmail.com> * * Copyright (c) 2009 Oleksandr Khayrullin <saniokh@gmail.com> * * Copyright (c) 2009-2010 Joffrey Clavel <jclavel@clabert.info> * * * * 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, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ #define DEBUG_PREFIX "SimilarArtistsEngine" #include "SimilarArtistsEngine.h" #include "EngineController.h" #include "core/meta/Meta.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include <QTimer> #include <QXmlStreamReader> AMAROK_EXPORT_DATAENGINE( similarArtists, SimilarArtistsEngine ) using namespace Context; SimilarArtistsEngine::SimilarArtistsEngine( QObject *parent, const QList<QVariant>& /*args*/ ) : DataEngine( parent ) , m_maxArtists( 5 ) { } SimilarArtistsEngine::~SimilarArtistsEngine() { } void SimilarArtistsEngine::init() { EngineController *engine = The::engineController(); connect( engine, SIGNAL(trackChanged(Meta::TrackPtr)), SLOT(update()) ); connect( engine, SIGNAL(trackMetadataChanged(Meta::TrackPtr)), SLOT(update()) ); } bool SimilarArtistsEngine::sourceRequestEvent( const QString &name ) { if( !name.startsWith( "similarArtists" ) ) return false; bool force( false ); QStringList tokens = name.split( QLatin1Char(':'), QString::SkipEmptyParts ); if( tokens.contains( QLatin1String("forceUpdate") ) ) force = true; if( tokens.contains( QLatin1String("artist") ) ) return update( m_artist ); else return update( force ); } bool SimilarArtistsEngine::update( bool force ) { QString newArtist; Meta::TrackPtr track = The::engineController()->currentTrack(); if( track ) { if( Meta::ArtistPtr artistPtr = track->artist() ) newArtist = artistPtr->name(); } if( newArtist.isEmpty() ) { m_artist.clear(); removeAllData( "similarArtists" ); return false; } else //valid artist { // wee make a request only if the artist is different if( force || (newArtist != m_artist) ) { // if the artist has changed m_artist = newArtist; similarArtistsRequest( m_artist ); return true; } } return false; } bool SimilarArtistsEngine::update( const QString &name ) { if( name.isEmpty() ) return false; m_artist = name; similarArtistsRequest( m_artist ); return true; } void SimilarArtistsEngine::similarArtistsRequest( const QString &artistName ) { // we generate the url for the demand on the lastFM Api - KUrl url; + QUrl url; url.setScheme( "http" ); url.setHost( "ws.audioscrobbler.com" ); url.setPath( "/2.0/" ); url.addQueryItem( "method", "artist.getSimilar" ); url.addQueryItem( "api_key", Amarok::lastfmApiKey() ); url.addQueryItem( "artist", artistName ); url.addQueryItem( "limit", QString::number( m_maxArtists ) ); The::networkAccessManager()->getData( url, this, - SLOT(parseSimilarArtists(KUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); + SLOT(parseSimilarArtists(QUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); } void -SimilarArtistsEngine::parseSimilarArtists( const KUrl &url, QByteArray data, +SimilarArtistsEngine::parseSimilarArtists( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ) { if( e.code != QNetworkReply::NoError ) { removeAllData( "similarArtists" ); warning() << "Failed to parse similar artists xml:" << url << e.description; return; } if( data.isEmpty() ) return; QXmlStreamReader xml( data ); SimilarArtist::List saList = SimilarArtist::listFromXml( xml ); debug() << "Found" << saList.size() << "similar artists to" << m_artist; Plasma::DataEngine::Data eData; eData[ "artist" ] = m_artist; eData[ "similar" ] = qVariantFromValue( saList ); setData( "similarArtists", eData ); } int SimilarArtistsEngine::maximumArtists() const { return m_maxArtists; } void SimilarArtistsEngine::setMaximumArtists( int number ) { m_maxArtists = number; } QString SimilarArtistsEngine::artist() const { return m_artist; } void SimilarArtistsEngine::setArtist( const QString &name ) { m_artist = name; } diff --git a/src/context/engines/similarartists/SimilarArtistsEngine.h b/src/context/engines/similarartists/SimilarArtistsEngine.h index 689ff9b01f..28ec98ce35 100644 --- a/src/context/engines/similarartists/SimilarArtistsEngine.h +++ b/src/context/engines/similarartists/SimilarArtistsEngine.h @@ -1,107 +1,107 @@ /*************************************************************************************** * Copyright (c) 2009 Nathan Sala <sala.nathan@gmail.com> * * Copyright (c) 2009 Oleksandr Khayrullin <saniokh@gmail.com> * * Copyright (c) 2009-2010 Joffrey Clavel <jclavel@clabert.info> * * * * 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, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ #ifndef SIMILARARTISTSENGINE_H #define SIMILARARTISTSENGINE_H #include "NetworkAccessManagerProxy.h" #include "context/DataEngine.h" #include "context/applets/similarartists/SimilarArtist.h" #include "core/meta/forward_declarations.h" using namespace Context; /** * This class provide SimilarArtists data for use in the SimilarArtists context applet. * It gets its information from the API lastfm. */ class SimilarArtistsEngine : public DataEngine { Q_OBJECT Q_PROPERTY( int maximumArtists READ maximumArtists WRITE setMaximumArtists ) Q_PROPERTY( QString artist READ artist WRITE setArtist ) public: /** * Construct the engine * @param parent The object parent to this engine */ SimilarArtistsEngine( QObject *parent, const QList<QVariant> &args ); virtual void init(); /** * Destroy the dataEngine */ virtual ~SimilarArtistsEngine(); /** * Fetches the similar artists for an artist thanks to the LastFM WebService * Store this in the similar artist list of this class * @param artistName the name of the artist */ void similarArtistsRequest( const QString &artistName ); /** * The maximum number of similar artists * @return number of similar artists */ int maximumArtists() const; /** * Set the maximum number of similar artists * @param number The maximum number of similar artists */ void setMaximumArtists( int number ); QString artist() const; void setArtist( const QString &name ); protected: bool sourceRequestEvent( const QString &name ); private: /** * The max number of similar artists to get */ int m_maxArtists; /** * The artist, whose research is similar artists. */ QString m_artist; private slots: /** * Update similar artists for the current playing track. * Launch when the track played on amarok has changed. * @param force force update to take place. */ bool update( bool force = false ); bool update( const QString &name ); /** * Parse the xml fetched on the lastFM API. * Launched when the download of the data are finished. */ - void parseSimilarArtists( const KUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ); + void parseSimilarArtists( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ); }; #endif // SIMILARARTISTSENGINE_H diff --git a/src/context/engines/songkick/SongkickEngine.cpp b/src/context/engines/songkick/SongkickEngine.cpp index 6851cd568f..22917196a3 100644 --- a/src/context/engines/songkick/SongkickEngine.cpp +++ b/src/context/engines/songkick/SongkickEngine.cpp @@ -1,166 +1,166 @@ /**************************************************************************************** * Copyright (c) 2008 Jeff Mitchell <mitchell@kde.org> * * Copyright (c) 2007-2008 Leo Franchi <lfranchi@gmail.com> * * * * 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, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ #include "SongkickEngine.h" #include "JsonQt/lib/JsonToVariant.h" #include "JsonQt/lib/ParseException.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include "ContextObserver.h" #include "ContextView.h" #include "EngineController.h" #include <KIO/Job> #include <QFile> #include <QLocale> #include <QUrl> using namespace Context; SongkickEngine::SongkickEngine( QObject* parent, const QList<QVariant>& args ) : DataEngine( parent ) , ContextObserver( ContextView::self() ) , m_datesJob( 0 ) , m_currentTrack( 0 ) , m_ontour( true ) , m_dates( true ) { Q_UNUSED( args ) DEBUG_BLOCK m_sources << I18N_NOOP( "ontour" ) << I18N_NOOP( "dates" ); } QStringList SongkickEngine::sources() const { DEBUG_BLOCK return m_sources; } bool SongkickEngine::sourceRequestEvent( const QString& name ) { DEBUG_BLOCK debug() << "sourceRequested with name " << name; removeAllData( name ); setData( name, "fetching" ); update(); return true; } void SongkickEngine::message( const ContextState& state ) { DEBUG_BLOCK if( state == Current ) update(); } void SongkickEngine::metadataChanged( Meta::TrackPtr track ) { Q_UNUSED( track ) DEBUG_BLOCK update(); } void SongkickEngine::update() { DEBUG_BLOCK unsubscribeFrom( m_currentTrack ); Meta::TrackPtr currentTrack = The::engineController()->currentTrack(); m_currentTrack = currentTrack; subscribeTo( currentTrack ); if ( !currentTrack ) { debug() << "No current track!"; return; } else if ( !currentTrack->artist() ) { debug() << "No artist found!"; return; } QString country = QLocale::system().name().right( 2 ).toLower(); - KUrl ontourUrl( QString( "http://api.songkick.com/api/V2/get_tour_status?key=kJcAUmzi8AoAngzh&id=0&country=%2&range=all&name=%1" ).arg( QUrl::toPercentEncoding( currentTrack->artist()->prettyName() ), country ) ); + QUrl ontourUrl( QString( "http://api.songkick.com/api/V2/get_tour_status?key=kJcAUmzi8AoAngzh&id=0&country=%2&range=all&name=%1" ).arg( QUrl::toPercentEncoding( currentTrack->artist()->prettyName() ), country ) ); debug() << "getting ontour status: " << ontourUrl; m_ontourJob = KIO::storedGet( ontourUrl, KIO::NoReload, KIO::HideProgressInfo ); connect( m_ontourJob, SIGNAL(result(KJob*)), this, SLOT(ontourResult(KJob*)) ); - KUrl datesUrl( QString( "http://api.songkick.com/api/V2/get_dates_extended?key=kJcAUmzi8AoAngzh&id=0&country=%2&range=all&name=%1" ).arg( QUrl::toPercentEncoding( currentTrack->artist()->prettyName() ), country ) ); + QUrl datesUrl( QString( "http://api.songkick.com/api/V2/get_dates_extended?key=kJcAUmzi8AoAngzh&id=0&country=%2&range=all&name=%1" ).arg( QUrl::toPercentEncoding( currentTrack->artist()->prettyName() ), country ) ); debug() << "getting concert dates: " << datesUrl; m_datesJob = KIO::storedGet( datesUrl, KIO::NoReload, KIO::HideProgressInfo ); connect( m_datesJob, SIGNAL(result(KJob*)), this, SLOT(datesResult(KJob*)) ); } void SongkickEngine::datesResult( KJob* job ) { DEBUG_BLOCK if( job != m_datesJob ) return; if( !m_datesJob ) return; if( !job->error() == 0 && m_datesJob == job) { setData( "dates", "error" ); return; } KIO::StoredTransferJob* const storedJob = static_cast<KIO::StoredTransferJob*>( job ); QString data = QString( storedJob->data() ); QVariantMap dates; /*QVariant datesResult = JsonQt::JsonToVariant::parse( data ); debug() << "got dates: " << dates; QMapIterator< QString, QVariant > iter( dates ); while( iter.hasNext() ) { iter.next(); setData( "dates", iter.key(), iter.value() ); } */ setData( "dates", data ); } void SongkickEngine::ontourResult( KJob* job ) { DEBUG_BLOCK if( job != m_ontourJob ) return; m_ontour = false; if( !m_ontourJob ) return; if( !job->error() == 0 && m_ontourJob == job ) { setData( "ontour", "error" ); return; } KIO::StoredTransferJob* const storedJob = static_cast<KIO::StoredTransferJob*>( job ); QString data = QString( storedJob->data() ); QVariantMap status; setData( "ontour", data ); } diff --git a/src/context/engines/tabs/TabsEngine.cpp b/src/context/engines/tabs/TabsEngine.cpp index 592a44e70f..87bcd34be8 100644 --- a/src/context/engines/tabs/TabsEngine.cpp +++ b/src/context/engines/tabs/TabsEngine.cpp @@ -1,511 +1,511 @@ /**************************************************************************************** * Copyright (c) 2010 Rainer Sigle <rainer.sigle@web.de> * * * * 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, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ #define DEBUG_PREFIX "TabsEngine" #include "TabsEngine.h" #include "EngineController.h" #include "context/ContextView.h" #include "core/meta/Meta.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include <QTextCodec> using namespace Context; /** * \brief Constructor * * Creates a new instance of the TabsEngine */ TabsEngine::TabsEngine( QObject* parent, const QList<QVariant>& /*args*/ ) : DataEngine( parent ) , m_fetchGuitar( true ) , m_fetchBass( true ) , m_numAbortedUrls( 0 ) { EngineController *engine = The::engineController(); connect( engine, SIGNAL(trackChanged(Meta::TrackPtr)), this, SLOT(update()) ); connect( engine, SIGNAL(trackMetadataChanged(Meta::TrackPtr)), this, SLOT(update()) ); } /** * \brief Destructor * * Destroys a TabsEngine instance */ TabsEngine::~TabsEngine() { DEBUG_BLOCK foreach( TabsInfo *info, m_tabs ) delete info; m_tabs.clear(); m_urls.clear(); } /** * Returns our sources */ QStringList TabsEngine::sources() const { // one source within the tabs-engine QStringList sources; sources << "tabs"; return sources; } bool TabsEngine::fetchGuitar() const { return m_fetchGuitar; } void TabsEngine::setFetchGuitar( const bool fetch ) { m_fetchGuitar = fetch; } bool TabsEngine::fetchBass() const { return m_fetchBass; } void TabsEngine::setFetchBass( const bool fetch ) { m_fetchBass = fetch; } QString TabsEngine::artistName() const { return m_artistName; } void TabsEngine::setArtistName( const QString &artistName ) { m_artistName = artistName; } QString TabsEngine::titleName() const { return m_titleName; } void TabsEngine::setTitleName( const QString &titleName ) { m_titleName = titleName; } bool TabsEngine::sourceRequestEvent( const QString &name ) { removeAllData( name ); setData( name, QVariant() ); QStringList tokens = name.split( QLatin1Char(':'), QString::SkipEmptyParts ); if( tokens.contains( QLatin1String( "forceUpdate" ) ) ) { // data coming from the applet configuration dialog m_titleName.clear(); m_artistName.clear(); update(); } else if( tokens.contains( QLatin1String( "forceUpdateSpecificTitleArtist" ) ) ) { // handle reload of a specific artist and title requestTab( m_artistName, m_titleName ); } else { // update on initial request update(); } return true; } /** * called whenever metadata of the current track has changed */ void TabsEngine::update() { DEBUG_BLOCK // get the current track Meta::TrackPtr track = The::engineController()->currentTrack(); if( !track ) { debug() << "no track"; m_titleName.clear(); m_artistName.clear(); removeAllData( "tabs" ); setData( "tabs", "state", "Stopped" ); return; } m_currentTrack = track; Meta::ArtistPtr artistPtr = track->artist(); QString newArtist; if( artistPtr ) { if( ( track->playableUrl().protocol() == "lastfm" ) || ( track->playableUrl().protocol() == "daap" ) || !The::engineController()->isStream() ) newArtist = artistPtr->name(); else newArtist = artistPtr->prettyName(); } QString newTitle = track->name(); if( newTitle.isEmpty() ) newTitle = track->prettyName(); // check if something changed if( newTitle == m_titleName && newArtist == m_artistName ) { debug() << "nothing changed"; return; } // stop fetching for unknown artists or titles if( newTitle.isEmpty() || newArtist.isEmpty() ) { setData("tabs", "state", "noTabs" ); return; } requestTab( newArtist, newTitle ); } /** * starts a new tab-search */ void TabsEngine::requestTab( const QString &artist, const QString &title ) { DEBUG_BLOCK debug() << "request tabs for artist: " << artist << " and title " << title; // clean all previously allocated stuff foreach( TabsInfo *tab, m_tabs ) delete tab; m_tabs.clear(); m_urls.clear(); m_numAbortedUrls = 0; removeAllData( "tabs" ); m_artistName = artist; m_titleName = title; // status update setData( "tabs", "state", "Fetching" ); setData( "tabs", "title", m_titleName ); setData( "tabs", "artist", m_artistName ); // define search criteria for the current artist/track QStringList artistSearchList = defineArtistSearchCriteria( artist ); QStringList titleSearchList = defineTitleSearchCriteria( title ); foreach( const QString &searchArtist, artistSearchList ) { foreach( const QString &searchTitle, titleSearchList ) { queryUltimateGuitar( searchArtist, searchTitle ); } } } /** * * starts a tab-search on UltimateGuitar.com */ void TabsEngine::queryUltimateGuitar( const QString &artist, const QString &title ) { // Query UltimateGuitar.com (filtering guitar (tabs + chords) and bass tabs) - KUrl ultimateGuitarUrl; + QUrl ultimateGuitarUrl; ultimateGuitarUrl.setScheme( "http" ); ultimateGuitarUrl.setHost( "www.ultimate-guitar.com" ); ultimateGuitarUrl.setPath( "/search.php" ); ultimateGuitarUrl.addQueryItem( "view_state", "advanced" ); ultimateGuitarUrl.addQueryItem( "band_name", artist ); ultimateGuitarUrl.addQueryItem( "song_name", title ); ultimateGuitarUrl.addQueryItem( "type%5B%5D", QString::number ( 200 ) ); // filter guitar tabs ultimateGuitarUrl.addQueryItem( "type%5B%5D", QString::number ( 300 ) ); // filter guitar chords ultimateGuitarUrl.addQueryItem( "type%5B%5D", QString::number ( 400 ) ); // filter bass tabs ultimateGuitarUrl.addQueryItem( "version_la", "" ); The::networkAccessManager()->getData( ultimateGuitarUrl, this, - SLOT(resultUltimateGuitarSearch(KUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); + SLOT(resultUltimateGuitarSearch(QUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); m_urls.insert( ultimateGuitarUrl ); } /** * parses the tab search results from UltimateGuitar */ void -TabsEngine::resultUltimateGuitarSearch( const KUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ) +TabsEngine::resultUltimateGuitarSearch( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ) { // specific job has finished -> remove from queue if( !m_urls.contains( url ) ) return; m_urls.remove( url ); // check if an error occurred during the HTTP-request if( netReplyError( e ) ) return; // get and parse the result const QString result( data ); const QString resultsTable = subStringBetween( result, "class=\"tresults\"", "</table>" ); if( !resultsTable.isEmpty() ) { const QStringList results = resultsTable.split( "</tr>" ); foreach ( const QString &result, results ) { // lastIndex on purpose (due to the fact that tabledata for the first result contains two hrefs) // get the link to the actual tab const QString tabUrl = subStringBetween( result, "a href=\"", "\" class", true ); if( !tabUrl.isEmpty() ) { // fetch the actual tab - const KUrl tabFetchUrl = KUrl( tabUrl ); + const QUrl tabFetchUrl = QUrl( tabUrl ); The::networkAccessManager()->getData( tabFetchUrl, this, - SLOT(resultUltimateGuitarTab(KUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); + SLOT(resultUltimateGuitarTab(QUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); m_urls.insert( tabFetchUrl ); } } } resultFinalize(); } /** * * retrieves the information for a single tab from UltimateGuitar.com */ void -TabsEngine::resultUltimateGuitarTab( const KUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ) +TabsEngine::resultUltimateGuitarTab( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ) { // specific tab search job has finished -> remove from queue if( !m_urls.contains( url ) ) return; m_urls.remove( url ); // check if an error occurred during the HTTP-request if( netReplyError( e ) ) return; // TODO: is this valid in all cases? // without fromLatin1, umlauts in german tabs are not displayed correctly QString result; if( QTextCodec::codecForUtfText( data )->name().contains( "ISO-8859-1" ) ) result = QString::fromLatin1( data ); else result = QString( data ); // extract tab title and data const QString title = subStringBetween( result, "<strong>", "</strong>" ); result.remove( subStringBetween( result, "<div class=\"dn\">", "</div>" ) ); QRegExp regex = QRegExp( "<pre>.*</pre>", Qt::CaseInsensitive ); if( regex.indexIn( result ) == -1 ) return; QString tabs = regex.cap(); tabs.remove( "<span>", Qt::CaseInsensitive ); tabs.remove( "</span>", Qt::CaseInsensitive ); TabsInfo::TabType tabType = TabsInfo::GUITAR; const QString tabTypeString = subStringBetween( result, "<title>", " by " ); if( tabTypeString.contains( "bass", Qt::CaseInsensitive ) ) tabType = TabsInfo::BASS; if( !tabs.isEmpty() ) { if( ( m_fetchGuitar && tabType == TabsInfo::GUITAR ) || ( m_fetchBass && tabType == TabsInfo::BASS ) ) { TabsInfo *item = new TabsInfo; item->url = url; item->tabType = tabType; item->title = title; item->tabs = tabs; item->source = "Ultimate-Guitar"; m_tabs << item; } } // update the results resultFinalize(); } /** * checks if all fetching jobs have finished and send the tab-data to the applet afterwards */ void TabsEngine::resultFinalize() { if( m_urls.count() > 0 ) return; // remove fetching state removeData( "tabs", "state" ); debug() << "Total # of fetched tabs: " << m_tabs.size(); if( m_numAbortedUrls > 0 ) { setData( "tabs", "state", "FetchError" ); return; } else if( m_tabs.size() == 0 ) { setData( "tabs", "state", "noTabs" ); return; } else { // sort against tabtype - QList < QPair < TabsInfo::TabType, KUrl > > sorting; + QList < QPair < TabsInfo::TabType, QUrl > > sorting; foreach( TabsInfo *item, m_tabs ) - sorting << QPair < TabsInfo::TabType, KUrl> ( item->tabType, item->url) ; - qSort(sorting.begin(), sorting.end(), qLess<QPair < TabsInfo::TabType, KUrl> >() ); + sorting << QPair < TabsInfo::TabType, QUrl> ( item->tabType, item->url) ; + qSort(sorting.begin(), sorting.end(), qLess<QPair < TabsInfo::TabType, QUrl> >() ); // debug info foreach( TabsInfo *item, m_tabs ) debug() << " Title: " << item->title << " (" << item->url << ")"; // if the song hasn't change while fetching, we sent the data if( m_currentTrack != The::engineController()->currentTrack() ) return; // otherwise send the fetched data to the subscribed applets - QList < QPair <TabsInfo::TabType, KUrl > >::iterator i; + QList < QPair <TabsInfo::TabType, QUrl > >::iterator i; int pos = 0; for(i = sorting.begin(); i != sorting.end(); ++i) { foreach( TabsInfo *item, m_tabs) { if( (*i).second == item->url ) { QVariant var; var.setValue<TabsInfo *>( item ); setData( "tabs", QString( "tabs:" ) + QString().setNum( pos ), var ); pos++; } } } } } /** * helper-function for html-parsing */ QString TabsEngine::subStringBetween( const QString &src, const QString &from, const QString &to, bool lastIndexForFrom ) { int startIdx; if( lastIndexForFrom ) startIdx = src.lastIndexOf( from ); else startIdx = src.indexOf( from ); if( startIdx == -1 ) return QString(); startIdx += from.length(); int endIdx = src.indexOf( to, startIdx ); if( endIdx == -1 ) return QString(); return src.mid( startIdx, endIdx - startIdx ); } /** * modifications on the artist to get more results */ QStringList TabsEngine::defineArtistSearchCriteria( const QString &artist ) { QStringList artists; QString searchArtist = artist.trimmed(); artists << searchArtist; // remove trailing "The" (otherwise no results for 'The Cure', 'The Smashing Pumpkins', ...) if( searchArtist.startsWith( "The ", Qt::CaseInsensitive ) ) artists << searchArtist.remove( "The ", Qt::CaseInsensitive ); return artists; } /** * modifications on the title to get more results */ QStringList TabsEngine::defineTitleSearchCriteria( const QString &title ) { QStringList titles; QString searchTitle = title.trimmed(); titles << searchTitle; // remove trailing "The" if( searchTitle.startsWith( "The ", Qt::CaseInsensitive ) ) titles << searchTitle.remove( "The ", Qt::CaseInsensitive ); // remove anything like (live), (demo-tape), ... QRegExp regex = QRegExp( "\\s*\\(.*\\)", Qt::CaseInsensitive ); if( regex.indexIn( searchTitle ) > 0 ) titles << searchTitle.remove( regex ); // remove anything like [xxxx]. regex = QRegExp( "\\s*\\[.*\\]", Qt::CaseInsensitive ); if( regex.indexIn( searchTitle ) > 0 ) titles << searchTitle.remove( regex ); return titles; } /** * checks if a tab-fetch job aborted with an error * returns true in case of error, false otherwise */ bool TabsEngine::netReplyError( NetworkAccessManagerProxy::Error e ) { // check the access manager network replay if( e.code == QNetworkReply::NoError ) { // at least one job successful, clear list of aborted urls m_numAbortedUrls = 0; return false; } else { // store url, list gets checked in resultFinalize m_numAbortedUrls++; resultFinalize(); return true; } } diff --git a/src/context/engines/tabs/TabsEngine.h b/src/context/engines/tabs/TabsEngine.h index 2041c993e2..34cedadad2 100644 --- a/src/context/engines/tabs/TabsEngine.h +++ b/src/context/engines/tabs/TabsEngine.h @@ -1,150 +1,150 @@ /**************************************************************************************** * Copyright (c) 2010 Rainer Sigle <rainer.sigle@web.de> * * * * 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, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ #ifndef AMAROK_TABS_ENGINE #define AMAROK_TABS_ENGINE #include "TabsInfo.h" #include "context/DataEngine.h" #include "core/meta/forward_declarations.h" #include "NetworkAccessManagerProxy.h" #include <QVariant> class KJob; using namespace Context; /** * This engine provides tab-data for the current song */ class TabsEngine : public DataEngine { Q_OBJECT Q_PROPERTY( QString artistName READ artistName WRITE setArtistName ) Q_PROPERTY( QString titleName READ titleName WRITE setTitleName ) Q_PROPERTY( bool fetchGuitarTabs READ fetchGuitar WRITE setFetchGuitar ) Q_PROPERTY( bool fetchBassTabs READ fetchBass WRITE setFetchBass ) public: TabsEngine( QObject* parent, const QList<QVariant>& args ); virtual ~TabsEngine(); QString artistName() const; void setArtistName( const QString &artistName ); QString titleName() const; void setTitleName( const QString &titleName ); bool fetchGuitar() const; void setFetchGuitar( const bool fetch ); bool fetchBass() const; void setFetchBass( const bool fetch ); QStringList sources() const; protected: bool sourceRequestEvent( const QString &name ); private slots: /** * handling of tab data search results from ultimateguitar.com */ - void resultUltimateGuitarSearch( const KUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ); - void resultUltimateGuitarTab( const KUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ); + void resultUltimateGuitarSearch( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ); + void resultUltimateGuitarTab( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ); /** * This method will send the info to the applet and order them if every jobs are finished */ void resultFinalize(); /** * Prepare the calling of the requestTab method. * Launched when the track played on amarok has changed. */ void update(); private: /** * starts a new tab-search */ void requestTab( const QString &artist, const QString &title ); /** * starts a tab search at ultimateguitar.com */ void queryUltimateGuitar( const QString &artist, const QString &title ); /** * The currently playing track */ Meta::TrackPtr m_currentTrack; /** * Data strucuture which contains all tab-information for * the current song. After fetching this data will be send to the applet */ QList < TabsInfo * > m_tabs; /** * Set containing urls of active jobs */ - QSet < const KUrl > m_urls; + QSet < const QUrl > m_urls; /** * Holds artist and title name of the current track */ QString m_titleName; QString m_artistName; /** * Controls whether guitar-tabs will be fetched */ bool m_fetchGuitar; /** * Controls whether bass-tabs will be fetched */ bool m_fetchBass; /** * Helper function which returns the intermediate string between two strings */ QString subStringBetween( const QString &src, const QString &from, const QString &to, bool lastIndexForFrom = false ); /** * returns a list of possible search criteria for the current artist */ QStringList defineArtistSearchCriteria( const QString &artist ); /** * returns a list of possible search criteria for the current title */ QStringList defineTitleSearchCriteria( const QString &title ); /** * checks if a tab-fetch job aborted with an error * returns true in case of error, false otherwise */ bool netReplyError( NetworkAccessManagerProxy::Error e ); int m_numAbortedUrls; }; Q_DECLARE_METATYPE ( TabsInfo * ) AMAROK_EXPORT_DATAENGINE( tabs, TabsEngine ) #endif diff --git a/src/context/engines/tabs/TabsInfo.h b/src/context/engines/tabs/TabsInfo.h index 5591c935bd..83137d53a2 100644 --- a/src/context/engines/tabs/TabsInfo.h +++ b/src/context/engines/tabs/TabsInfo.h @@ -1,33 +1,33 @@ /**************************************************************************************** * Copyright (c) 2010 Rainer Sigle <rainer.sigle@web.de> * * * * 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, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ #ifndef AMAROK_TABS_INFO #define AMAROK_TABS_INFO -#include <KUrl> +#include <QUrl> // struct TabsInfo contains all the data for a tab struct TabsInfo { enum TabType { GUITAR, BASS, DRUM, PIANO }; QString title; // Name of the specific tab QString tabs; // Data for the tab QString source; // origin for the tab TabType tabType; // TabType for the tab - KUrl url; // Url of the specific tab + QUrl url; // Url of the specific tab }; #endif diff --git a/src/context/engines/upcomingevents/UpcomingEventsEngine.cpp b/src/context/engines/upcomingevents/UpcomingEventsEngine.cpp index d1fbe6485a..2f3b0a03e3 100644 --- a/src/context/engines/upcomingevents/UpcomingEventsEngine.cpp +++ b/src/context/engines/upcomingevents/UpcomingEventsEngine.cpp @@ -1,213 +1,213 @@ /**************************************************************************************** * Copyright (c) 2009 Oleksandr Khayrullin <saniokh@gmail.com> * * Copyright (c) 2009 Nathan Sala <sala.nathan@gmail.com> * * Copyright (c) 2009-2010 Ludovic Deveaux <deveaux.ludovic31@gmail.com> * * Copyright (c) 2010 Hormiere Guillaume <hormiere.guillaume@gmail.com> * * * * 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, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ #define DEBUG_PREFIX "UpcomingEventsEngine" #include "UpcomingEventsEngine.h" #include "context/ContextView.h" #include "context/applets/upcomingevents/LastFmEventXmlParser.h" #include "core/meta/Meta.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include "EngineController.h" #include <KDateTime> #include <QXmlStreamReader> AMAROK_EXPORT_DATAENGINE( upcomingEvents, UpcomingEventsEngine ) using namespace Context; UpcomingEventsEngine::UpcomingEventsEngine( QObject* parent, const QList<QVariant>& /*args*/ ) : DataEngine( parent ) { m_timeSpan = Amarok::config("UpcomingEvents Applet").readEntry( "timeSpan", "AllEvents" ); EngineController *engine = The::engineController(); connect( engine, SIGNAL(trackChanged(Meta::TrackPtr)), this, SLOT(updateDataForArtist()) ); connect( engine, SIGNAL(trackMetadataChanged(Meta::TrackPtr)), this, SLOT(updateDataForArtist()) ); } UpcomingEventsEngine::~UpcomingEventsEngine() { } bool UpcomingEventsEngine::sourceRequestEvent( const QString &source ) { if( source == "artistevents" ) { updateDataForArtist(); return false; // data is not ready yet, but will be soon } else if( source == "venueevents" ) { m_venueIds.clear(); QStringList venues = Amarok::config("UpcomingEvents Applet").readEntry( "favVenues", QStringList() ); foreach( const QString &venue, venues ) { QStringList frag = venue.split( QChar(';') ); m_venueIds << frag.at( 0 ).toInt(); } updateDataForVenues(); return true; } else if( source == "venueevents:update" ) { removeAllData( source ); sourceRequestEvent( "venueevents" ); } else if( source == "timespan:update" ) { // user has changed the timespan. m_timeSpan = Amarok::config("UpcomingEvents Applet").readEntry( "timeSpan", "AllEvents" ); sourceRequestEvent( "venueevents:update" ); updateDataForArtist(); return true; } return false; } void UpcomingEventsEngine::updateDataForVenues() { if( !m_venueIds.isEmpty() ) { int id = m_venueIds.takeFirst(); - KUrl url; + QUrl url; url.setScheme( "http" ); url.setHost( "ws.audioscrobbler.com" ); url.setPath( "/2.0/" ); url.addQueryItem( "method", "venue.getEvents" ); url.addQueryItem( "api_key", Amarok::lastfmApiKey() ); url.addQueryItem( "venue", QString::number( id ) ); The::networkAccessManager()->getData( url, this, - SLOT(venueEventsFetched(KUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); + SLOT(venueEventsFetched(QUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); QTimer::singleShot( 50, this, SLOT(updateDataForVenues()) ); } } void UpcomingEventsEngine::updateDataForArtist() { Meta::TrackPtr track = The::engineController()->currentTrack(); if( !track ) return; Meta::ArtistPtr artist = track->artist(); if( !artist || artist == m_currentArtist || artist->name().isEmpty() ) return; m_currentArtist = artist; // Prepares the url for LastFm request m_urls.clear(); - KUrl url; + QUrl url; url.setScheme( "http" ); url.setHost( "ws.audioscrobbler.com" ); url.setPath( "/2.0/" ); url.addQueryItem( "method", "artist.getEvents" ); url.addQueryItem( "api_key", Amarok::lastfmApiKey() ); url.addQueryItem( "artist", m_currentArtist->name() ); m_urls << url; The::networkAccessManager()->getData( url, this, - SLOT(artistEventsFetched(KUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); + SLOT(artistEventsFetched(QUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); } void -UpcomingEventsEngine::artistEventsFetched( const KUrl &url, QByteArray data, +UpcomingEventsEngine::artistEventsFetched( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ) { if( !m_urls.contains( url ) ) return; m_urls.remove( url ); if( e.code != QNetworkReply::NoError ) { debug() << "Error received getting upcoming artist events" << e.description; return; } QXmlStreamReader xml( data ); LastFmEventXmlParser eventsParser( xml ); removeAllData( "artistevents" ); Plasma::DataEngine::Data engineData; if( eventsParser.read() ) { LastFmEvent::List artistEvents = filterEvents( eventsParser.events() ); engineData[ "artist" ] = m_currentArtist->name(); engineData[ "events" ] = qVariantFromValue( artistEvents ); } setData( "artistevents", engineData ); } void -UpcomingEventsEngine::venueEventsFetched( const KUrl &url, QByteArray data, +UpcomingEventsEngine::venueEventsFetched( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ) { Q_UNUSED( url ) if( e.code != QNetworkReply::NoError ) { debug() << "Error received getting upcoming venue events" << e.description; return; } QXmlStreamReader xml( data ); LastFmEventXmlParser eventsParser( xml ); Plasma::DataEngine::Data engineData; if( eventsParser.read() ) { LastFmEvent::List venueEvents = filterEvents( eventsParser.events() ); if( !venueEvents.isEmpty() ) { engineData[ "venue" ] = qVariantFromValue( venueEvents.first()->venue() ); engineData[ "events" ] = qVariantFromValue( venueEvents ); } } setData( "venueevents", engineData ); } LastFmEvent::List UpcomingEventsEngine::filterEvents( const LastFmEvent::List &events ) const { KDateTime currentTime( KDateTime::currentLocalDateTime() ); if( m_timeSpan == "ThisWeek") currentTime = currentTime.addDays( 7 ); else if( m_timeSpan == "ThisMonth" ) currentTime = currentTime.addMonths( 1 ); else if( m_timeSpan == "ThisYear" ) currentTime = currentTime.addYears( 1 ); else return events; // no filtering is done LastFmEvent::List newEvents; foreach( const LastFmEventPtr &event, events ) { if( event->date() < currentTime ) newEvents << event; } return newEvents; } diff --git a/src/context/engines/upcomingevents/UpcomingEventsEngine.h b/src/context/engines/upcomingevents/UpcomingEventsEngine.h index 9b7d86df6e..b122375c84 100644 --- a/src/context/engines/upcomingevents/UpcomingEventsEngine.h +++ b/src/context/engines/upcomingevents/UpcomingEventsEngine.h @@ -1,110 +1,110 @@ /**************************************************************************************** * Copyright (c) 2009 Oleksandr Khayrullin <saniokh@gmail.com> * * Copyright (c) 2009 Nathan Sala <sala.nathan@gmail.com> * * Copyright (c) 2009-2010 Ludovic Deveaux <deveaux.ludovic31@gmail.com> * * * * 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, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ #ifndef AMAROK_UPCOMINGEVENTS_ENGINE #define AMAROK_UPCOMINGEVENTS_ENGINE #include "context/applets/upcomingevents/LastFmEvent.h" #include "context/DataEngine.h" #include "core/meta/forward_declarations.h" #include "network/NetworkAccessManagerProxy.h" // Qt #include <QDomDocument> #include <QLocale> #include <QXmlStreamReader> class QNetworkReply; using namespace Context; /** * \class UpcomingEventsEngine * * This class provide UpcomingEvents data for use in Context applets */ class UpcomingEventsEngine : public DataEngine { Q_OBJECT public: /** * \brief Constructor * * Creates a new instance of UpcomingEventsEngine */ UpcomingEventsEngine( QObject* parent, const QList<QVariant>& args ); /** * \brief Destructor * * Destroys an UpcomingEventsEngine instance */ virtual ~UpcomingEventsEngine(); protected: /** * Reimplemented from Plasma::DataEngine */ bool sourceRequestEvent( const QString &name ); private: /** * filterEvents filters a list of events depending on settings * @param events a list of events to filter * @return a new list of events that satisfies filter settings */ LastFmEvent::List filterEvents( const LastFmEvent::List &events ) const; /** * The value can be "AllEvents", "ThisWeek", "ThisMonth" or "ThisYear" */ QString m_timeSpan; /** * The current artist */ Meta::ArtistPtr m_currentArtist; /** * Current URLs of events being fetched */ - QSet<KUrl> m_urls; + QSet<QUrl> m_urls; /** * @param ids LastFm's venue ids */ QList<int> m_venueIds; private slots: /** * Get events for specific artist */ void updateDataForArtist(); /** * Get events for specific venues */ void updateDataForVenues(); - void artistEventsFetched( const KUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ); - void venueEventsFetched( const KUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ); + void artistEventsFetched( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ); + void venueEventsFetched( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ); }; #endif diff --git a/src/context/engines/wikipedia/WikipediaEngine.cpp b/src/context/engines/wikipedia/WikipediaEngine.cpp index ce8c436813..ca4ede6bf4 100644 --- a/src/context/engines/wikipedia/WikipediaEngine.cpp +++ b/src/context/engines/wikipedia/WikipediaEngine.cpp @@ -1,963 +1,963 @@ /**************************************************************************************** * Copyright (c) 2012 Ryan McCoskrie <ryan.mccoskrie@gmail.com * * Copyright (c) 2007 Leo Franchi <lfranchi@gmail.com> * * Copyright (c) 2008 Mark Kretschmann <kretschmann@kde.org> * * Copyright (c) 2009 Simon Esneault <simon.esneault@gmail.com> * * * * 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, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ #define DEBUG_PREFIX "WikipediaEngine" #include "WikipediaEngine.h" #include "EngineController.h" #include "core/meta/Meta.h" #include "core/meta/support/MetaConstants.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include <Plasma/DataContainer> #include <QHashIterator> #include <QXmlStreamReader> using namespace Context; class WikipediaEnginePrivate { private: WikipediaEngine *const q_ptr; Q_DECLARE_PUBLIC( WikipediaEngine ) public: WikipediaEnginePrivate( WikipediaEngine *parent ) : q_ptr( parent ) , currentSelection( Artist ) , useMobileVersion( false ) , useSSL( true ) , dataContainer( 0 ) {} ~WikipediaEnginePrivate() {} enum SelectionType { Artist, Composer, Album, Track }; // functions void fetchWikiUrl( const QString &title, const QString &urlPrefix ); void fetchLangLinks( const QString &title, const QString &hostLang, const QString &llcontinue = QString() ); void fetchListing( const QString &title, const QString &hostLang ); void reloadWikipedia(); bool setSelection( SelectionType type ); // returns true if selection is changed bool setSelection( const QString &type ); SelectionType selection() const; void updateEngine(); void wikiParse( QString &page ); QString createLanguageComboBox( const QMap<QString, QString> &languageMap ); // data members SelectionType currentSelection; QUrl wikiCurrentUrl; QStringList preferredLangs; struct TrackMetadata { QString artist; QString composer; QString album; QString track; void clear() { artist.clear(); composer.clear(); album.clear(); track.clear(); } } m_previousTrackMetadata; bool useMobileVersion; bool useSSL; Plasma::DataContainer *dataContainer; QSet< QUrl > urls; // private slots void _checkRequireUpdate( Meta::TrackPtr track ); void _dataContainerUpdated( const QString &source, const Plasma::DataEngine::Data &data ); - void _parseLangLinksResult( const KUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ); - void _parseListingResult( const KUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ); - void _wikiResult( const KUrl &url, QByteArray result, NetworkAccessManagerProxy::Error e ); + void _parseLangLinksResult( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ); + void _parseListingResult( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ); + void _wikiResult( const QUrl &url, QByteArray result, NetworkAccessManagerProxy::Error e ); void _stopped(); }; void WikipediaEnginePrivate::_dataContainerUpdated( const QString &source, const Plasma::DataEngine::Data &data ) { DEBUG_BLOCK Q_Q( WikipediaEngine ); if( source != QLatin1String("wikipedia") ) return; if( data.isEmpty() ) { debug() << "data is empty"; return; } if( data.contains( QLatin1String("reload") ) ) { if( data.value( QLatin1String("reload") ).toBool() ) { debug() << QLatin1String("reloading"); reloadWikipedia(); } q->removeData( source, QLatin1String("reload") ); } if( data.contains( QLatin1String("goto") ) ) { QString gotoType = data.value( QLatin1String("goto") ).toString(); debug() << "goto:" << gotoType; if( !gotoType.isEmpty() ) { setSelection( gotoType ); q->setData( source, QLatin1String("busy"), true ); updateEngine(); } q->removeData( source, QLatin1String("goto") ); } if( data.contains( QLatin1String("clickUrl") ) ) { QUrl clickUrl = data.value( QLatin1String("clickUrl") ).toUrl(); debug() << "clickUrl:" << clickUrl; if( clickUrl.isValid() ) { wikiCurrentUrl = clickUrl; if( !wikiCurrentUrl.hasQueryItem( QLatin1String("useskin") ) ) wikiCurrentUrl.addQueryItem( QLatin1String("useskin"), QLatin1String("monobook") ); - KUrl encodedUrl( wikiCurrentUrl.toEncoded() ); + QUrl encodedUrl( wikiCurrentUrl.toEncoded() ); urls << encodedUrl; q->setData( source, QLatin1String("busy"), true ); The::networkAccessManager()->getData( encodedUrl, q, - SLOT(_wikiResult(KUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); + SLOT(_wikiResult(QUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); } q->removeData( source, QLatin1String("clickUrl") ); } if( data.contains( QLatin1String("mobile") ) ) { bool mobile = data.value( QLatin1String("mobile") ).toBool(); if( mobile != useMobileVersion ) { debug() << (mobile ? "switching to mobile wikipedia" : "switching to normal wikipedia"); useMobileVersion = mobile; updateEngine(); } } if( data.contains( QLatin1String("ssl") ) ) { const bool ssl = data.value( QLatin1String("ssl") ).toBool(); if( ssl != useSSL ) { useSSL = ssl; updateEngine(); } } if( data.contains( QLatin1String("lang") ) ) { QStringList langList = data.value( QLatin1String("lang") ).toStringList(); if( !langList.isEmpty() && (preferredLangs != langList) ) { preferredLangs = langList; updateEngine(); debug() << QLatin1String("updated preferred wikipedia languages:") << preferredLangs; } q->removeData( source, QLatin1String("lang") ); } } void -WikipediaEnginePrivate::_wikiResult( const KUrl &url, QByteArray result, NetworkAccessManagerProxy::Error e ) +WikipediaEnginePrivate::_wikiResult( const QUrl &url, QByteArray result, NetworkAccessManagerProxy::Error e ) { Q_Q( WikipediaEngine ); if( !urls.contains( url ) ) return; urls.remove( url ); if( e.code != QNetworkReply::NoError ) { q->removeAllData( QLatin1String("wikipedia") ); q->setData( QLatin1String("wikipedia"), QLatin1String("message"), i18n("Unable to retrieve Wikipedia information: %1", e.description) ); q->scheduleSourcesUpdated(); return; } debug() << "Received page from wikipedia:" << url; QString wiki( result ); // FIXME: For now we test if we got an article or not with a test on this string "wgArticleId=0" // This is bad if( wiki.contains(QLatin1String("wgArticleId=0")) && (wiki.contains(QLatin1String("wgNamespaceNumber=0")) || wiki.contains(QLatin1String("wgPageName=\"Special:Badtitle\"")) ) ) // The article does not exist { debug() << "article does not exist"; q->removeAllData( QLatin1String("wikipedia") ); q->setData( QLatin1String("wikipedia"), QLatin1String("message"), i18n( "No information found..." ) ); q->scheduleSourcesUpdated(); return; } // We've found a page DataEngine::Data data; wikiParse( wiki ); data[QLatin1String("page")] = wiki; data[QLatin1String("url")] = QUrl(url); q->removeData( QLatin1String("wikipedia"), QLatin1String("busy") ); Meta::TrackPtr currentTrack = The::engineController()->currentTrack(); if( !currentTrack ) return; if( currentSelection == Artist ) // default, or applet told us to fetch artist { if( currentTrack && currentTrack->artist() ) { data[QLatin1String("label")] = QLatin1String("Artist"); data[QLatin1String("title")] = currentTrack->artist()->prettyName(); } } else if( currentSelection == Composer ) { data[QLatin1String("label")] = QLatin1String("Title"); data[QLatin1String("title")] = currentTrack->composer()->prettyName(); } else if( currentSelection == Track ) { data[QLatin1String("label")] = QLatin1String("Title"); data[QLatin1String("title")] = currentTrack->prettyName(); } else if( currentSelection == Album ) { if( currentTrack && currentTrack->album() ) { data[QLatin1String("label")] = QLatin1String("Album"); data[QLatin1String("title")] = currentTrack->album()->prettyName(); } } q->setData( QLatin1String("wikipedia"), data ); q->scheduleSourcesUpdated(); } void -WikipediaEnginePrivate::_parseLangLinksResult( const KUrl &url, QByteArray data, +WikipediaEnginePrivate::_parseLangLinksResult( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ) { Q_Q( WikipediaEngine ); if( !urls.contains( url ) ) return; urls.remove( url ); if( e.code != QNetworkReply::NoError || data.isEmpty() ) { debug() << "Parsing langlinks result failed" << e.description; q->removeAllData( QLatin1String("wikipedia") ); q->setData( QLatin1String("wikipedia"), QLatin1String("message"), i18n("Unable to retrieve Wikipedia information: %1", e.description) ); q->scheduleSourcesUpdated(); return; } QString hostLang = url.host(); hostLang.remove( QLatin1String(".wikipedia.org") ); const QString &title = url.queryItemValue( QLatin1String("titles") ); QHash<QString, QString> langTitleMap; // a hash of langlinks and their titles QString llcontinue; QXmlStreamReader xml( data ); while( !xml.atEnd() && !xml.hasError() ) { xml.readNext(); if( xml.isStartElement() && xml.name() == QLatin1String("page") ) { if( xml.attributes().hasAttribute(QLatin1String("missing")) ) break; QXmlStreamAttributes a = xml.attributes(); if( a.hasAttribute(QLatin1String("pageid")) && a.hasAttribute(QLatin1String("title")) ) { const QString &pageTitle = a.value( QLatin1String("title") ).toString(); if( pageTitle.endsWith(QLatin1String("(disambiguation)")) ) { fetchListing( title, hostLang ); return; } langTitleMap[hostLang] = title; } while( !xml.atEnd() ) { xml.readNext(); if( xml.isEndElement() && xml.name() == QLatin1String("page") ) break; if( xml.isStartElement() ) { if( xml.name() == QLatin1String("ll") ) { QXmlStreamAttributes a = xml.attributes(); if( a.hasAttribute(QLatin1String("lang")) ) { QString lang = a.value( QLatin1String("lang") ).toString(); langTitleMap[lang] = xml.readElementText(); } } else if( xml.name() == QLatin1String("query-continue") ) { xml.readNext(); if( xml.isStartElement() && xml.name() == QLatin1String("langlinks") ) { QXmlStreamAttributes a = xml.attributes(); if( a.hasAttribute(QLatin1String("llcontinue")) ) llcontinue = a.value( QLatin1String("llcontinue") ).toString(); } } } } } } if( !langTitleMap.isEmpty() ) { /* When we query langlinks using a particular language, interwiki * results will not contain links for that language. However, it may * appear as part of the "page" element if there's a match or a redirect * has been set. So we need to manually add it here if it's still empty. */ if( preferredLangs.contains(hostLang) && !langTitleMap.contains(hostLang) ) langTitleMap[hostLang] = title; q->removeData( QLatin1String("wikipedia"), QLatin1String("busy") ); QStringListIterator langIter( preferredLangs ); while( langIter.hasNext() ) { QString prefix = langIter.next().split( QLatin1Char(':') ).back(); if( langTitleMap.contains(prefix) ) { QString pageTitle = langTitleMap.value( prefix ); fetchListing( pageTitle, prefix ); return; } } } if( !llcontinue.isEmpty() ) { fetchLangLinks( title, hostLang, llcontinue ); } else { QRegExp regex( QLatin1Char('^') + hostLang + QLatin1String(".*$") ); int index = preferredLangs.indexOf( regex ); if( (index != -1) && (index < preferredLangs.count() - 1) ) { // use next preferred language as base for fetching langlinks since // the current one did not get any results we want. QString prefix = preferredLangs.value( index + 1 ).split( QLatin1Char(':') ).back(); fetchLangLinks( title, prefix ); } else { QStringList refinePossibleLangs = preferredLangs.filter( QRegExp("^(en|fr|de|pl).*$") ); if( refinePossibleLangs.isEmpty() ) { q->removeAllData( QLatin1String("wikipedia") ); q->setData( QLatin1String("wikipedia"), QLatin1String("message"), i18n( "No information found..." ) ); q->scheduleSourcesUpdated(); return; } fetchListing( title, refinePossibleLangs.first().split( QLatin1Char(':') ).back() ); } } } void -WikipediaEnginePrivate::_parseListingResult( const KUrl &url, +WikipediaEnginePrivate::_parseListingResult( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ) { Q_Q( WikipediaEngine ); if( !urls.contains( url ) ) return; urls.remove( url ); if( e.code != QNetworkReply::NoError || data.isEmpty() ) { debug() << "Parsing listing result failed" << e.description; q->removeAllData( QLatin1String("wikipedia") ); q->setData( QLatin1String("wikipedia"), QLatin1String("message"), i18n("Unable to retrieve Wikipedia information: %1", e.description) ); q->scheduleSourcesUpdated(); return; } QString hostLang = url.host(); hostLang.remove( QLatin1String(".wikipedia.org") ); const QString &title = url.queryItemValue( QLatin1String("srsearch") ); QStringList titles; QXmlStreamReader xml( data ); while( !xml.atEnd() && !xml.hasError() ) { xml.readNext(); if( xml.isStartElement() && xml.name() == QLatin1String("search") ) { while( xml.readNextStartElement() ) { if( xml.name() == QLatin1String("p") ) { if( xml.attributes().hasAttribute( QLatin1String("title") ) ) titles << xml.attributes().value( QLatin1String("title") ).toString(); xml.skipCurrentElement(); } else xml.skipCurrentElement(); } } } if( titles.isEmpty() ) { QStringList refinePossibleLangs = preferredLangs.filter( QRegExp("^(en|fr|de|pl).*$") ); int index = refinePossibleLangs.indexOf( hostLang ); if( (index != -1) && (index < refinePossibleLangs.count() - 1) ) fetchListing( title, refinePossibleLangs.value( index + 1 ).split( QLatin1Char(':') ).back() ); else { q->removeAllData( QLatin1String("wikipedia") ); q->setData( QLatin1String("wikipedia"), QLatin1String("message"), i18n( "No information found..." ) ); } return; } QString pattern; switch( currentSelection ) { default: case Artist: pattern = i18nc("Search pattern for an artist or band", ".*\\(.*(artist|band).*\\))").toLatin1(); break; case Composer: pattern = i18nc("Search pattern for a composer", ".*\\(.*(composer|musician).*\\))").toLatin1(); break; case Album: pattern = i18nc("Search pattern for an album", ".*\\(.*(album|score|soundtrack).*\\)").toLatin1(); break; case Track: pattern = i18nc("Search pattern for a song", ".*\\(.*(song|track).*\\)").toLatin1(); break; } pattern.prepend( title ); int patternIndex = titles.indexOf( QRegExp(pattern, Qt::CaseInsensitive) ); const QString result = ( patternIndex != -1 ) ? titles.at( patternIndex ) : titles.first(); fetchWikiUrl( result, hostLang ); // end of the line } void WikipediaEnginePrivate::_checkRequireUpdate( Meta::TrackPtr track ) { if( !track ) return; bool updateNeeded(false); switch( currentSelection ) { case WikipediaEnginePrivate::Artist: if( track->artist() ) updateNeeded = track->artist()->name() != m_previousTrackMetadata.artist; break; case WikipediaEnginePrivate::Composer: if( track->composer() ) updateNeeded = track->composer()->name() != m_previousTrackMetadata.composer; break; case WikipediaEnginePrivate::Album: if( track->album() ) updateNeeded = track->album()->name() != m_previousTrackMetadata.album; break; case WikipediaEnginePrivate::Track: updateNeeded = track->name() != m_previousTrackMetadata.track; break; } if( updateNeeded ) { m_previousTrackMetadata.clear(); if( track->artist() ) m_previousTrackMetadata.artist = track->artist()->name(); if( track->composer() ) m_previousTrackMetadata.composer = track->composer()->name(); if( track->album() ) m_previousTrackMetadata.album = track->album()->name(); m_previousTrackMetadata.track = track->name(); urls.clear(); updateEngine(); } } void WikipediaEnginePrivate::_stopped() { DEBUG_BLOCK Q_Q( WikipediaEngine ); dataContainer->removeAllData(); dataContainer->setData( "stopped", 1 ); q->scheduleSourcesUpdated(); m_previousTrackMetadata.clear(); } void WikipediaEnginePrivate::fetchWikiUrl( const QString &title, const QString &urlPrefix ) { Q_Q( WikipediaEngine ); - KUrl pageUrl; + QUrl pageUrl; QString host( ".wikipedia.org" ); pageUrl.setScheme( useSSL ? QLatin1String( "https" ) : QLatin1String( "http" ) ); if( useMobileVersion ) { host.prepend( ".m" ); host.prepend( urlPrefix ); pageUrl.setHost( host ); pageUrl.setPath( QString("/wiki/%1").arg(title) ); DataEngine::Data data; data[QLatin1String("sourceUrl")] = pageUrl; q->removeAllData( QLatin1String("wikipedia") ); q->setData( QLatin1String("wikipedia"), data ); q->scheduleSourcesUpdated(); return; } // We now use: http://en.wikipedia.org/w/index.php?title=The_Beatles&useskin=monobook // instead of: http://en.wikipedia.org/wiki/The_Beatles // So that wikipedia skin is forced to default "monoskin", and the page can be parsed correctly (see BUG 205901 ) host.prepend( urlPrefix ); pageUrl.setHost( host ); pageUrl.setPath( QLatin1String("/w/index.php") ); pageUrl.addQueryItem( QLatin1String("title"), title ); pageUrl.addQueryItem( QLatin1String("redirects"), QString::number(1) ); pageUrl.addQueryItem( QLatin1String("useskin"), QLatin1String("monobook") ); wikiCurrentUrl = pageUrl; urls << pageUrl; The::networkAccessManager()->getData( pageUrl, q, - SLOT(_wikiResult(KUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); + SLOT(_wikiResult(QUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); } void WikipediaEnginePrivate::fetchLangLinks( const QString &title, const QString &hostLang, const QString &llcontinue ) { Q_Q( WikipediaEngine ); - KUrl url; + QUrl url; url.setScheme( useSSL ? QLatin1String( "https" ) : QLatin1String( "http" ) ); url.setHost( hostLang + QLatin1String(".wikipedia.org") ); url.setPath( QLatin1String("/w/api.php") ); url.addQueryItem( QLatin1String("action"), QLatin1String("query") ); url.addQueryItem( QLatin1String("prop"), QLatin1String("langlinks") ); url.addQueryItem( QLatin1String("titles"), title ); url.addQueryItem( QLatin1String("format"), QLatin1String("xml") ); url.addQueryItem( QLatin1String("lllimit"), QString::number(100) ); url.addQueryItem( QLatin1String("redirects"), QString::number(1) ); if( !llcontinue.isEmpty() ) url.addQueryItem( QLatin1String("llcontinue"), llcontinue ); urls << url; debug() << "Fetching langlinks:" << url; The::networkAccessManager()->getData( url, q, - SLOT(_parseLangLinksResult(KUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); + SLOT(_parseLangLinksResult(QUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); } void WikipediaEnginePrivate::fetchListing( const QString &title, const QString &hostLang ) { Q_Q( WikipediaEngine ); - KUrl url; + QUrl url; url.setScheme( useSSL ? QLatin1String( "https" ) : QLatin1String( "http" ) ); url.setHost( hostLang + QLatin1String(".wikipedia.org") ); url.setPath( QLatin1String("/w/api.php") ); url.addQueryItem( QLatin1String("action"), QLatin1String("query") ); url.addQueryItem( QLatin1String("list"), QLatin1String("search") ); url.addQueryItem( QLatin1String("srsearch"), title ); url.addQueryItem( QLatin1String("srprop"), QLatin1String("size") ); url.addQueryItem( QLatin1String("srredirects"), QString::number(1) ); url.addQueryItem( QLatin1String("srlimit"), QString::number(20) ); url.addQueryItem( QLatin1String("format"), QLatin1String("xml") ); urls << url; debug() << "Fetching listing:" << url; The::networkAccessManager()->getData( url, q, - SLOT(_parseListingResult(KUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); + SLOT(_parseListingResult(QUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); } void WikipediaEnginePrivate::updateEngine() { static QMap<SelectionType, qint64> typeToFieldMap; if( typeToFieldMap.isEmpty() ) { typeToFieldMap.insert( Artist, Meta::valArtist ); typeToFieldMap.insert( Composer, Meta::valComposer ); typeToFieldMap.insert( Album, Meta::valAlbum ); typeToFieldMap.insert( Track, Meta::valTitle ); } Q_Q( WikipediaEngine ); Meta::TrackPtr currentTrack = The::engineController()->currentTrack(); if( !currentTrack ) return; //TODO: Look into writing one function that can be used with different arguments for each case in this switch. QString tmpWikiStr; QString notice = i18nc( "%1 is field name such as 'Artist Name'", "%1 is needed for searching Wikipedia.", Meta::i18nForField( typeToFieldMap.value( currentSelection ) ) ); switch( currentSelection ) { case Artist: if( currentTrack->artist() ) { if( currentTrack->artist()->name().isEmpty() ) { q->removeAllData( QLatin1String("wikipedia") ); q->scheduleSourcesUpdated(); q->setData( QLatin1String("wikipedia"), QLatin1String("message"), notice ); return; } if( ( currentTrack->playableUrl().protocol() == QLatin1String("lastfm") ) || ( currentTrack->playableUrl().protocol() == QLatin1String("daap") ) || !The::engineController()->isStream() ) tmpWikiStr = currentTrack->artist()->name(); else tmpWikiStr = currentTrack->artist()->prettyName(); } break; case Composer: if( currentTrack->composer() ) { if( currentTrack->composer()->name().isEmpty() ) { q->removeAllData( QLatin1String("wikipedia") ); q->scheduleSourcesUpdated(); q->setData( QLatin1String("wikipedia"), QLatin1String("message"), notice ); return; } if( ( currentTrack->playableUrl().protocol() == QLatin1String("lastfm") ) || ( currentTrack->playableUrl().protocol() == QLatin1String("daap") ) || !The::engineController()->isStream() ) tmpWikiStr = currentTrack->composer()->name(); } break; case Album: if( currentTrack->album() ) { if( currentTrack->album()->name().isEmpty() ) { q->removeAllData( QLatin1String("wikipedia") ); q->scheduleSourcesUpdated(); q->setData( QLatin1String("wikipedia"), QLatin1String("message"), notice ); return; } if( ( currentTrack->playableUrl().protocol() == QLatin1String("lastfm") ) || ( currentTrack->playableUrl().protocol() == QLatin1String("daap") ) || !The::engineController()->isStream() ) tmpWikiStr = currentTrack->album()->name(); } break; case Track: if( currentTrack->name().isEmpty() ) { q->removeAllData( QLatin1String("wikipedia") ); q->scheduleSourcesUpdated(); q->setData( QLatin1String("wikipedia"), QLatin1String("message"), notice ); return; } tmpWikiStr = currentTrack->prettyName(); break; } //Hack to make wiki searches work with magnatune preview tracks if( tmpWikiStr.contains( QLatin1String("PREVIEW: buy it at www.magnatune.com") ) ) { tmpWikiStr = tmpWikiStr.remove(QLatin1String(" (PREVIEW: buy it at www.magnatune.com)") ); int index = tmpWikiStr.indexOf( QLatin1Char('-') ); if( index != -1 ) tmpWikiStr = tmpWikiStr.left (index - 1); } if( preferredLangs.isEmpty() ) preferredLangs = QStringList() << QLatin1String("en:en"); fetchLangLinks( tmpWikiStr, preferredLangs.first().split( QLatin1Char(':') ).back() ); } void WikipediaEnginePrivate::wikiParse( QString &wiki ) { //remove the new-lines and tabs(replace with spaces IS needed). wiki.replace( '\n', QLatin1Char(' ') ); wiki.replace( '\t', QLatin1Char(' ') ); // Get the available language list QString wikiLanguagesSection; QMap<QString, QString> langMap; int langSectionIndex = 0; if( (langSectionIndex = wiki.indexOf( QLatin1String("<div id=\"p-lang\" class=\"portlet\">") )) != -1 ) { QStringRef sref = wiki.midRef( langSectionIndex ); sref = wiki.midRef( sref.position(), wiki.indexOf( QLatin1String("<ul>"), sref.position() ) - sref.position() ); sref = wiki.midRef( sref.position(), wiki.indexOf( QLatin1String("</dev>"), sref.position() ) - sref.position() ); wikiLanguagesSection = sref.toString(); QXmlStreamReader xml( wikiLanguagesSection ); while( !xml.atEnd() && !xml.hasError() ) { xml.readNext(); if( xml.isStartElement() && xml.name() == QLatin1String("li") ) { while( xml.readNextStartElement() ) { if( xml.name() == QLatin1String("a") ) { QString url = xml.attributes().value( QLatin1String("href") ).toString(); langMap[ xml.readElementText() ] = url; } else xml.skipCurrentElement(); } } } } QString copyright; const QString copyrightMark = QLatin1String("<li id=\"f-copyright\">"); int copyrightIndex = wiki.indexOf( copyrightMark ); if( copyrightIndex != -1 ) { QStringRef sref = wiki.midRef( copyrightIndex + copyrightMark.length() ); sref = wiki.midRef( sref.position(), wiki.indexOf( QLatin1String("</li>"), sref.position() ) - sref.position() ); copyright = sref.toString(); copyright.remove( QLatin1String("<br />") ); //only one br at the beginning copyright.prepend( QLatin1String("<br />") ); } const int titleIndex = wiki.indexOf( QRegExp( QLatin1String("<title>[^<]*") ) ) + 7; const int bsTitleIndex = wiki.indexOf( QLatin1String(""), titleIndex ) - titleIndex; const QString title = wiki.mid( titleIndex, bsTitleIndex ); // Ok lets remove the top and bottom parts of the page QStringRef wikiRef; wikiRef = wiki.midRef( wiki.indexOf( QLatin1String("") ) ); wikiRef = wiki.midRef( wikiRef.position(), wiki.indexOf( QLatin1String("
"), wikiRef.position() ) - wikiRef.position() ); wiki = wikiRef.toString(); auto removeTag = [&wiki] ( const QString& tagStart, const QString& tagEnd ) { const int tagEndSize = tagEnd.size(); int matchIndex = 0; const QStringMatcher tagMatcher( tagStart ); while( ( matchIndex = tagMatcher.indexIn( wiki, matchIndex ) ) != -1 ) { const int nToTagEnd = wiki.indexOf( tagEnd, matchIndex ) - matchIndex; const QStringRef tagRef = wiki.midRef( matchIndex, nToTagEnd + tagEndSize ); wiki.remove( tagRef.toString() ); } }; // lets remove the warning box removeTag( "" ); // remove protection policy (we don't do edits) removeTag( "
" ); // lets also remove the "lock" image removeTag( "" ); // remove
") ); wiki.remove( QRegExp( QLatin1String("

[^<]*

") ) ); wiki.remove( QRegExp( QLatin1String("]*>[^<]*<[^>]*>[^<]*<[^>]*>[^<]*") ) ); wiki.remove( QRegExp( QLatin1String("

]*><[^\"]*\"#_skip_noteTA\">[^<]*<[^<]*

") ) ); wiki.replace( QRegExp( QLatin1String("
]*>([^<]*)") ), QLatin1String("\\1") ); // Remove anything inside of a class called urlexpansion, as it's pointless for us wiki.remove( QRegExp( QLatin1String("[^(]*[(][^)]*[)]") ) ); // Remove hidden table rows as well QRegExp hidden( QLatin1String("
.*"), Qt::CaseInsensitive ); hidden.setMinimal( true ); //greedy behaviour wouldn't be any good! wiki.remove( hidden ); // we want to keep our own style (we need to modify the stylesheet a bit to handle things nicely) wiki.remove( QRegExp( QLatin1String("style= *\"[^\"]*\"") ) ); // We need to leave the classes behind, otherwise styling it ourselves gets really nasty and tedious and roughly impossible to do in a sane maner //wiki.replace( QRegExp( "class= *\"[^\"]*\"" ), QString() ); // let's remove the form elements, we don't want them. wiki.remove( QRegExp( QLatin1String("]*>") ) ); wiki.remove( QRegExp( QLatin1String("]*>") ) ); wiki.remove( QLatin1String("\n") ); wiki.remove( QRegExp( QLatin1String("]*>") ) ); wiki.remove( QLatin1String("\n") ); wiki.remove( QRegExp( QLatin1String("]*>") ) ); wiki.remove( QLatin1String("") ); wiki.prepend( QLatin1String("\n") ); wiki.append( QString(QLatin1String("%1\n")).arg(title) ); wiki.append( QLatin1String("\n") ); // wiki.append( createLanguageComboBox(langMap) ); // BUG:259075 wiki.append( QLatin1String("\n") ); } QString WikipediaEnginePrivate::createLanguageComboBox( const QMap &languageMap ) { if( languageMap.isEmpty() ) return QString(); QString html; QMapIterator i(languageMap); while( i.hasNext() ) { i.next(); html += QString( "" ).arg( i.value(), i.key() ); } html.prepend( QString("
") ); return html; } void WikipediaEnginePrivate::reloadWikipedia() { Q_Q( WikipediaEngine ); if( !wikiCurrentUrl.isValid() ) return; urls << wikiCurrentUrl; q->setData( QLatin1String("wikipedia"), QLatin1String("busy"), true ); q->scheduleSourcesUpdated(); The::networkAccessManager()->getData( wikiCurrentUrl, q, - SLOT(_wikiResult(KUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); + SLOT(_wikiResult(QUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); } WikipediaEnginePrivate::SelectionType WikipediaEnginePrivate::selection() const { return currentSelection; } bool WikipediaEnginePrivate::setSelection( SelectionType type ) { if( currentSelection != type ) { currentSelection = type; return true; } return false; } bool WikipediaEnginePrivate::setSelection( const QString &type ) { bool changed( false ); if( type == QLatin1String("artist") ) changed = setSelection( Artist ); else if( type == QLatin1String("composer") ) changed = setSelection( Composer ); else if( type == QLatin1String("album") ) changed = setSelection( Album ); else if( type == QLatin1String("track") ) changed = setSelection( Track ); return changed; } WikipediaEngine::WikipediaEngine( QObject* parent, const QList& /*args*/ ) : DataEngine( parent ) , d_ptr( new WikipediaEnginePrivate( this ) ) { } WikipediaEngine::~WikipediaEngine() { delete d_ptr; } void WikipediaEngine::init() { Q_D( WikipediaEngine ); d->dataContainer = new Plasma::DataContainer( this ); d->dataContainer->setObjectName( QLatin1String("wikipedia") ); addSource( d->dataContainer ); connect( d->dataContainer, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)), this, SLOT(_dataContainerUpdated(QString,Plasma::DataEngine::Data)) ); EngineController *engine = The::engineController(); connect( engine, SIGNAL(trackChanged(Meta::TrackPtr)), this, SLOT(_checkRequireUpdate(Meta::TrackPtr)) ); connect( engine, SIGNAL(trackMetadataChanged(Meta::TrackPtr)), this, SLOT(_checkRequireUpdate(Meta::TrackPtr)) ); connect( engine, SIGNAL(stopped(qint64,qint64)), this, SLOT(_stopped()) ); } bool WikipediaEngine::sourceRequestEvent( const QString &source ) { if( source == QLatin1String("update") ) { scheduleSourcesUpdated(); } else if( source == QLatin1String("wikipedia") ) { Q_D( WikipediaEngine ); d->updateEngine(); return true; } return false; } diff --git a/src/context/engines/wikipedia/WikipediaEngine.h b/src/context/engines/wikipedia/WikipediaEngine.h index 2a18c0b175..1d3f63160a 100644 --- a/src/context/engines/wikipedia/WikipediaEngine.h +++ b/src/context/engines/wikipedia/WikipediaEngine.h @@ -1,69 +1,69 @@ /**************************************************************************************** * Copyright (c) 2007 Leo Franchi * * Copyright (c) 2008 Mark Kretschmann * * Copyright (c) 2009 Simon Esneault * * * * 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, see . * ****************************************************************************************/ #ifndef AMAROK_WIKIPEDIA_ENGINE #define AMAROK_WIKIPEDIA_ENGINE #include "core/meta/forward_declarations.h" #include "context/DataEngine.h" #include "NetworkAccessManagerProxy.h" /** This class provide Wikipedia data for use in Context applets. NOTE: The QVariant data is structured like this: * the key name is the artist * the data is a QString containing the html of the wikipedia page */ using namespace Context; namespace Plasma { class DataContainer; } class WikipediaEnginePrivate; class WikipediaEngine : public DataEngine { Q_OBJECT public: WikipediaEngine( QObject* parent, const QList& args ); virtual ~WikipediaEngine(); virtual void init(); protected: bool sourceRequestEvent( const QString &source ); private: WikipediaEnginePrivate *const d_ptr; Q_DECLARE_PRIVATE( WikipediaEngine ) Q_PRIVATE_SLOT( d_ptr, void _checkRequireUpdate(Meta::TrackPtr) ) Q_PRIVATE_SLOT( d_ptr, void _dataContainerUpdated(const QString&,const Plasma::DataEngine::Data&) ) - Q_PRIVATE_SLOT( d_ptr, void _wikiResult(const KUrl&,QByteArray,NetworkAccessManagerProxy::Error) ) - Q_PRIVATE_SLOT( d_ptr, void _parseLangLinksResult(const KUrl&,QByteArray,NetworkAccessManagerProxy::Error) ) - Q_PRIVATE_SLOT( d_ptr, void _parseListingResult(const KUrl&,QByteArray,NetworkAccessManagerProxy::Error) ) + Q_PRIVATE_SLOT( d_ptr, void _wikiResult(const QUrl&,QByteArray,NetworkAccessManagerProxy::Error) ) + Q_PRIVATE_SLOT( d_ptr, void _parseLangLinksResult(const QUrl&,QByteArray,NetworkAccessManagerProxy::Error) ) + Q_PRIVATE_SLOT( d_ptr, void _parseListingResult(const QUrl&,QByteArray,NetworkAccessManagerProxy::Error) ) Q_PRIVATE_SLOT( d_ptr, void _stopped() ) }; AMAROK_EXPORT_DATAENGINE( wikipedia, WikipediaEngine ) #endif diff --git a/src/context/widgets/DropPixmapItem.cpp b/src/context/widgets/DropPixmapItem.cpp index b573b97e21..9a6b82b1bf 100644 --- a/src/context/widgets/DropPixmapItem.cpp +++ b/src/context/widgets/DropPixmapItem.cpp @@ -1,158 +1,158 @@ /**************************************************************************************** * Copyright (c) 2009 Simon Esneault * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "DropPixmapItem" #include "DropPixmapItem.h" #include "core/support/Debug.h" #include #include DropPixmapItem::DropPixmapItem( QGraphicsItem* parent ) : QGraphicsPixmapItem( parent ) { setAcceptDrops( true ); } void DropPixmapItem::dropEvent(QGraphicsSceneDragDropEvent* event) { DEBUG_BLOCK if( event->mimeData()->hasText() ) { QString file( event->mimeData()->text() ); debug() << "dropped:" << file; if ( file.contains( "http://" ) || file.contains( "https://" ) ) { - m_url = KUrl( file ); + m_url = QUrl( file ); The::networkAccessManager()->getData( m_url, this, - SLOT(imageDownloadResult(KUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); + SLOT(imageDownloadResult(QUrl,QByteArray,NetworkAccessManagerProxy::Error)) ); } else if ( file.contains( "file://" ) ) { file.remove( "file://" ); QPixmap cover; cover.load( file ); if ( !cover.isNull() ) { emit imageDropped( cover ); } else { debug() << "not an image"; } } } if( event->mimeData()->hasImage() ) { debug() << "mimeData has image"; emit imageDropped( qVariantValue< QPixmap >( event->mimeData()->imageData() ) ); } } -void DropPixmapItem::imageDownloadResult( const KUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ) +void DropPixmapItem::imageDownloadResult( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ) { if( !url.isValid() || m_url != url ) return; m_url.clear(); if( e.code != QNetworkReply::NoError ) { debug() << "unable to download the image:" << e.description; return; } QPixmap cover; if( cover.loadFromData( data ) ) emit imageDropped( cover ); else debug() << "not an image"; } DropPixmapLayoutItem::DropPixmapLayoutItem( QGraphicsLayoutItem *parent, bool isLayout ) : QGraphicsLayoutItem( parent, isLayout ) { m_pixmap = new DropPixmapItem; m_pixmap->setZValue( 100 ); setGraphicsItem( m_pixmap ); connect( m_pixmap, SIGNAL(imageDropped(QPixmap)), SIGNAL(imageDropped(QPixmap)) ); } DropPixmapLayoutItem::~DropPixmapLayoutItem() { delete m_pixmap; } void DropPixmapLayoutItem::setGeometry( const QRectF &rect ) { QGraphicsLayoutItem::setGeometry( rect ); int width = m_pixmap->pixmap().width(); int height = m_pixmap->pixmap().height(); QPointF pos = rect.topLeft(); pos.rx() += (rect.width() - width) / 2; pos.ry() += (rect.height() - height) / 2; m_pixmap->setPos( pos ); } QSizeF DropPixmapLayoutItem::sizeHint( Qt::SizeHint which, const QSizeF &constraint ) const { Q_UNUSED( which ) Q_UNUSED( constraint ) return m_pixmap->boundingRect().size(); } qreal DropPixmapLayoutItem::opacity() const { return m_pixmap->opacity(); } void DropPixmapLayoutItem::setOpacity( qreal value ) { m_pixmap->setOpacity( value ); } QPixmap DropPixmapLayoutItem::pixmap() const { return m_pixmap->pixmap(); } void DropPixmapLayoutItem::setPixmap( const QPixmap &pixmap ) { m_pixmap->setPixmap( pixmap ); } void DropPixmapLayoutItem::show() { m_pixmap->show(); } void DropPixmapLayoutItem::hide() { m_pixmap->hide(); } diff --git a/src/context/widgets/DropPixmapItem.h b/src/context/widgets/DropPixmapItem.h index ffe5e3aab6..73ba060bfa 100644 --- a/src/context/widgets/DropPixmapItem.h +++ b/src/context/widgets/DropPixmapItem.h @@ -1,100 +1,100 @@ /**************************************************************************************** * Copyright (c) 2009 Simon Esneault * * * * 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, see . * ****************************************************************************************/ #ifndef DROPPIXMAPITEM_H #define DROPPIXMAPITEM_H #include "amarok_export.h" #include "network/NetworkAccessManagerProxy.h" -#include +#include #include #include //forward class QGraphicsSceneDragDropEvent; /** * \brief A QGraphicsPixmapItem on which you can drop an image * * Used for drag'n drop support for the cover. Will download the file if it's a link (from webrowser) * * \sa QGraphicsPixmapItem * * \author Simon Esneault */ class AMAROK_EXPORT DropPixmapItem : public QObject, public QGraphicsPixmapItem { Q_OBJECT public: DropPixmapItem( QGraphicsItem* parent = 0 ); signals: void imageDropped( const QPixmap &pixmap ); public slots: /** * Result of the image fetching stuff */ - void imageDownloadResult( const KUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ); + void imageDownloadResult( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ); protected slots: /** * Reimplement dropEvent */ virtual void dropEvent( QGraphicsSceneDragDropEvent* ); private: - KUrl m_url; + QUrl m_url; }; class AMAROK_EXPORT DropPixmapLayoutItem : public QObject, public QGraphicsLayoutItem { Q_OBJECT Q_INTERFACES( QGraphicsLayoutItem ) Q_PROPERTY( QPixmap pixmap READ pixmap WRITE setPixmap ) Q_PROPERTY( qreal opacity READ opacity WRITE setOpacity ) public: explicit DropPixmapLayoutItem( QGraphicsLayoutItem *parent = 0, bool isLayout = false ); virtual ~DropPixmapLayoutItem(); virtual void setGeometry( const QRectF &rect ); qreal opacity() const; void setOpacity( qreal value ); QPixmap pixmap() const; void setPixmap( const QPixmap &pixmap ); void show(); void hide(); signals: void imageDropped( const QPixmap &pixmap ); protected: virtual QSizeF sizeHint( Qt::SizeHint which, const QSizeF &constraint = QSizeF() ) const; private: DropPixmapItem *m_pixmap; }; #endif // DROPPIXMAPITEM_H diff --git a/src/context/widgets/RecentlyPlayedListWidget.cpp b/src/context/widgets/RecentlyPlayedListWidget.cpp index 65ac6fb94a..2a52eeea86 100644 --- a/src/context/widgets/RecentlyPlayedListWidget.cpp +++ b/src/context/widgets/RecentlyPlayedListWidget.cpp @@ -1,256 +1,256 @@ /**************************************************************************************** * Copyright (c) 2010 Rick W. Chen * * Copyright (c) 2013 Konrad Zemek * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "RecentlyPlayedListWidget" #include "RecentlyPlayedListWidget.h" #include "EngineController.h" #include "core/meta/Meta.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include "core-impl/collections/support/CollectionManager.h" #include "playlist/PlaylistController.h" #include #include #include #include #include #include #include #include #include #include #include ClickableGraphicsWidget::ClickableGraphicsWidget( const QString &url, QGraphicsItem *parent, Qt::WindowFlags wFlags ) : QGraphicsWidget( parent, wFlags ) , m_url( url ) { setAcceptHoverEvents( true ); setCursor( Qt::PointingHandCursor ); } ClickableGraphicsWidget::~ClickableGraphicsWidget() { } void ClickableGraphicsWidget::hoverEnterEvent( QGraphicsSceneHoverEvent *event ) { Q_UNUSED( event ) setOpacity( 0.5 ); update(); } void ClickableGraphicsWidget::hoverLeaveEvent( QGraphicsSceneHoverEvent *event ) { Q_UNUSED( event ) setOpacity( 1 ); update(); } void ClickableGraphicsWidget::mousePressEvent( QGraphicsSceneMouseEvent *event ) { Q_UNUSED( event ) } void ClickableGraphicsWidget::mouseReleaseEvent( QGraphicsSceneMouseEvent *event ) { if( !m_url.isEmpty() ) { if( event->button() == Qt::LeftButton ) emit leftClicked( m_url ); else if( event->button() == Qt::MidButton ) emit middleClicked( m_url ); } } TimeDifferenceLabel::TimeDifferenceLabel( const QDateTime &eventTime , QWidget *parent, Qt::WindowFlags wFlags ) : QLabel( parent, wFlags ) , m_eventTime( eventTime ) { update(); } TimeDifferenceLabel::~TimeDifferenceLabel() { } void TimeDifferenceLabel::update() { setText( Amarok::verboseTimeSince( m_eventTime ) ); } RecentlyPlayedListWidget::RecentlyPlayedListWidget( QGraphicsWidget *parent ) : Plasma::ScrollWidget( parent ) , m_layout( new QGraphicsLinearLayout( Qt::Vertical ) ) , m_trackIcon( KIcon( "media-album-track") ) { QGraphicsWidget *content = new QGraphicsWidget; content->setLayout( m_layout ); setWidget( content ); connect( EngineController::instance(), SIGNAL(trackChanged(Meta::TrackPtr)), SLOT(trackChanged(Meta::TrackPtr)) ); m_updateTimer = new QTimer( this ); m_updateTimer->start( 20 * 1000 ); // Load saved data const KConfigGroup group = Amarok::config( "Recently Played" ); const QVariantList recentlyPlayed = group.readEntry( "Last Played Dates", QVariantList() ); const QStringList displayNames = group.readEntry( "Display Names", QStringList() ); const QStringList trackUrls = group.readEntry( "Urls", QStringList() ); for( int i = 0; i < trackUrls.size(); ++i ) addTrack( recentlyPlayed[i].toDateTime(), displayNames[i], trackUrls[i] ); } RecentlyPlayedListWidget::~RecentlyPlayedListWidget() { QVariantList recentlyPlayed; QStringList displayNames; QStringList trackUrls; foreach( const RecentlyPlayedTrackData &data, m_recentTracks ) { recentlyPlayed.append( data.recentlyPlayed ); displayNames.append( data.displayName ); trackUrls.append( data.trackUrl ); } KConfigGroup group = Amarok::config( "Recently Played" ); group.writeEntry( "Last Played Dates", recentlyPlayed ); group.writeEntry( "Display Names", displayNames ); group.writeEntry( "Urls", trackUrls ); group.sync(); } QGraphicsWidget* RecentlyPlayedListWidget::addWidgetItem( const RecentlyPlayedTrackData &data ) { KSqueezedTextLabel *squeezer = new KSqueezedTextLabel( data.displayName ); squeezer->setTextElideMode( Qt::ElideRight ); squeezer->setAttribute( Qt::WA_NoSystemBackground ); squeezer->setCursor( Qt::PointingHandCursor ); QGraphicsProxyWidget *labelWidget = new QGraphicsProxyWidget; labelWidget->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred ); labelWidget->setWidget( squeezer ); TimeDifferenceLabel *lastPlayed = new TimeDifferenceLabel( data.recentlyPlayed ); lastPlayed->setAttribute( Qt::WA_NoSystemBackground ); lastPlayed->setAlignment( Qt::AlignRight ); lastPlayed->setWordWrap( false ); lastPlayed->setCursor( Qt::PointingHandCursor ); connect( m_updateTimer, SIGNAL(timeout()), lastPlayed, SLOT(update()) ); QGraphicsProxyWidget *lastPlayedWidget = new QGraphicsProxyWidget; lastPlayedWidget->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Preferred ); lastPlayedWidget->setWidget( lastPlayed ); Plasma::IconWidget *icon = new Plasma::IconWidget; QSizeF iconSize = icon->sizeFromIconSize( QFontMetricsF(QFont()).height() ); icon->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); icon->setMinimumSize( iconSize ); icon->setMaximumSize( iconSize ); icon->setIcon( m_trackIcon ); QGraphicsLinearLayout *itemLayout = new QGraphicsLinearLayout( Qt::Horizontal ); itemLayout->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ); itemLayout->setContentsMargins( 0, 0, 0, 0 ); itemLayout->addItem( icon ); itemLayout->addItem( labelWidget ); itemLayout->addItem( lastPlayedWidget ); ClickableGraphicsWidget *itemWidget = new ClickableGraphicsWidget( data.trackUrl ); itemWidget->setLayout( itemLayout ); connect( itemWidget, SIGNAL(leftClicked(QString)), SLOT(itemLeftClicked(QString)) ); connect( itemWidget, SIGNAL(middleClicked(QString)), SLOT(itemMiddleClicked(QString)) ); m_layout->insertItem( 0, itemWidget ); return itemWidget; } void RecentlyPlayedListWidget::itemLeftClicked( const QString &url ) { - Playlist::Controller::instance()->insertOptioned( KUrl( url ), + Playlist::Controller::instance()->insertOptioned( QUrl( url ), Playlist::OnDoubleClickOnSelectedItems ); } void RecentlyPlayedListWidget::itemMiddleClicked( const QString &url ) { - Playlist::Controller::instance()->insertOptioned( KUrl( url ), + Playlist::Controller::instance()->insertOptioned( QUrl( url ), Playlist::OnMiddleClickOnSelectedItems ); } void RecentlyPlayedListWidget::addTrack( const Meta::TrackPtr &track ) { const Meta::ArtistPtr artist = track->artist(); const QString displayName = !artist || artist->prettyName().isEmpty() ? track->prettyName() : i18nc( "%1 is artist, %2 is title", "%1 - %2", artist->prettyName(), track->prettyName() ); addTrack( QDateTime::currentDateTime(), displayName, track->uidUrl() ); } void RecentlyPlayedListWidget::addTrack( const QDateTime &recentlyPlayed, const QString &displayName, const QString &trackUrl ) { while( m_recentTracks.size() >= 10 ) { // Get rid of the least recent entry RecentlyPlayedTrackData data = m_recentTracks.dequeue(); delete data.widget; } RecentlyPlayedTrackData data; data.recentlyPlayed = recentlyPlayed; data.displayName = displayName; data.trackUrl = trackUrl; data.widget = addWidgetItem( data ); m_recentTracks.enqueue( data ); } void RecentlyPlayedListWidget::trackChanged( const Meta::TrackPtr &track ) { // Nothing has changed if( m_currentTrack == track ) return; // lastTrack will be null if we're resuming from a stopped state Meta::TrackPtr lastTrack = m_currentTrack; m_currentTrack = track; if( lastTrack ) addTrack( lastTrack ); } diff --git a/src/context/widgets/RecentlyPlayedListWidget.h b/src/context/widgets/RecentlyPlayedListWidget.h index 3c1081dccf..a84552ddea 100644 --- a/src/context/widgets/RecentlyPlayedListWidget.h +++ b/src/context/widgets/RecentlyPlayedListWidget.h @@ -1,109 +1,109 @@ /**************************************************************************************** * Copyright (c) 2010 Rick W. Chen * * Copyright (c) 2013 Konrad Zemek * * * * 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, see . * ****************************************************************************************/ #ifndef RECENTLY_PLAYED_LIST_WIDGET_H #define RECENTLY_PLAYED_LIST_WIDGET_H #include "core/meta/forward_declarations.h" #include -#include +#include #include #include #include #include #include class QGraphicsLinearLayout; class ClickableGraphicsWidget : public QGraphicsWidget { Q_OBJECT public: explicit ClickableGraphicsWidget( const QString &url, QGraphicsItem *parent = 0, Qt::WindowFlags wFlags = 0 ); ~ClickableGraphicsWidget(); signals: void leftClicked( const QString &url ); void middleClicked( const QString &url ); protected: void hoverEnterEvent( QGraphicsSceneHoverEvent *event ); void hoverLeaveEvent( QGraphicsSceneHoverEvent *event ); void mousePressEvent( QGraphicsSceneMouseEvent *event ); void mouseReleaseEvent( QGraphicsSceneMouseEvent *event ); private: const QString m_url; }; class TimeDifferenceLabel : public QLabel { Q_OBJECT public: explicit TimeDifferenceLabel( const QDateTime &eventTime, QWidget *parent = 0, Qt::WindowFlags wFlags = 0 ); ~TimeDifferenceLabel(); public slots: void update(); private: const QDateTime m_eventTime; }; class RecentlyPlayedListWidget : public Plasma::ScrollWidget { Q_OBJECT struct RecentlyPlayedTrackData { QDateTime recentlyPlayed; QString displayName; QString trackUrl; QGraphicsWidget *widget; }; public: explicit RecentlyPlayedListWidget( QGraphicsWidget *parent = 0 ); ~RecentlyPlayedListWidget(); private slots: void itemLeftClicked( const QString &url ); void itemMiddleClicked( const QString &url ); void trackChanged( const Meta::TrackPtr &track ); private: Q_DISABLE_COPY( RecentlyPlayedListWidget ) void addTrack( const Meta::TrackPtr &track ); void addTrack( const QDateTime &recentlyPlayed, const QString &displayName, const QString &trackUrl ); QGraphicsWidget *addWidgetItem( const RecentlyPlayedTrackData &data ); Meta::TrackPtr m_currentTrack; QGraphicsLinearLayout *m_layout; QQueue m_recentTracks; KIcon m_trackIcon; QTimer *m_updateTimer; }; #endif // RECENTLY_PLAYED_LIST_WIDGET_H diff --git a/src/core-impl/capabilities/multisource/MultiSourceCapabilityImpl.cpp b/src/core-impl/capabilities/multisource/MultiSourceCapabilityImpl.cpp index ad9ab0a22e..849f47b13f 100644 --- a/src/core-impl/capabilities/multisource/MultiSourceCapabilityImpl.cpp +++ b/src/core-impl/capabilities/multisource/MultiSourceCapabilityImpl.cpp @@ -1,57 +1,57 @@ /**************************************************************************************** * Copyright (c) 2009 Nikolaj Hald Nielsen * * * * 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, see . * ****************************************************************************************/ #include "MultiSourceCapabilityImpl.h" #include "core/support/Debug.h" using namespace Capabilities; MultiSourceCapabilityImpl::MultiSourceCapabilityImpl(Meta::MultiTrack * track) : Capabilities::MultiSourceCapability() , m_track( track ) { //forward from track, as there might be several instances of MultiSourceCapabilityImpl active for one track. - connect( m_track, SIGNAL(urlChanged(KUrl)), this, SIGNAL(urlChanged(KUrl)) ); + connect( m_track, SIGNAL(urlChanged(QUrl)), this, SIGNAL(urlChanged(QUrl)) ); } MultiSourceCapabilityImpl::~MultiSourceCapabilityImpl() { } QStringList MultiSourceCapabilityImpl::sources() const { return m_track->sources(); } void MultiSourceCapabilityImpl::setSource( int source ) { m_track->setSource( source ); } int MultiSourceCapabilityImpl::current() const { return m_track->current(); } -KUrl +QUrl MultiSourceCapabilityImpl::nextUrl() const { return m_track->nextUrl(); } diff --git a/src/core-impl/capabilities/multisource/MultiSourceCapabilityImpl.h b/src/core-impl/capabilities/multisource/MultiSourceCapabilityImpl.h index c8c4636a86..6d4acc3305 100644 --- a/src/core-impl/capabilities/multisource/MultiSourceCapabilityImpl.h +++ b/src/core-impl/capabilities/multisource/MultiSourceCapabilityImpl.h @@ -1,44 +1,44 @@ /**************************************************************************************** * Copyright (c) 2009 Nikolaj Hald Nielsen * * * * 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, see . * ****************************************************************************************/ #ifndef AMAROK_MULTISOURCECAPABILITYIMPL_P_H #define AMAROK_MULTISOURCECAPABILITYIMPL_P_H #include "core/capabilities/MultiSourceCapability.h" #include "core-impl/meta/multi/MultiTrack.h" namespace Capabilities { class MultiSourceCapabilityImpl : public MultiSourceCapability { Q_OBJECT public: MultiSourceCapabilityImpl( Meta::MultiTrack *track ); virtual ~MultiSourceCapabilityImpl(); virtual QStringList sources() const; virtual void setSource( int source ); virtual int current() const; - virtual KUrl nextUrl() const; + virtual QUrl nextUrl() const; private: Meta::MultiTrack *m_track; }; } #endif diff --git a/src/core-impl/collections/aggregate/AggregateCollection.cpp b/src/core-impl/collections/aggregate/AggregateCollection.cpp index 2e1431f709..b925fa06d2 100644 --- a/src/core-impl/collections/aggregate/AggregateCollection.cpp +++ b/src/core-impl/collections/aggregate/AggregateCollection.cpp @@ -1,526 +1,526 @@ /**************************************************************************************** * Copyright (c) 2009 Maximilian Kossick * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "AggregateCollection" #include "AggregateCollection.h" #include "core/support/Debug.h" #include "core-impl/collections/aggregate/AggregateMeta.h" #include "core-impl/collections/aggregate/AggregateQueryMaker.h" #include "core-impl/collections/support/CollectionManager.h" #include #include #include using namespace Collections; AggregateCollection::AggregateCollection() : Collections::Collection() { QTimer *timer = new QTimer( this ); timer->setSingleShot( false ); timer->setInterval( 60000 ); //clean up every 60 seconds connect( timer, SIGNAL(timeout()), this, SLOT(emptyCache()) ); timer->start(); } AggregateCollection::~AggregateCollection() { } QString AggregateCollection::prettyName() const { return i18nc( "Name of the virtual collection that merges tracks from all collections", "Aggregate Collection" ); } KIcon AggregateCollection::icon() const { return KIcon("drive-harddisk"); } bool -AggregateCollection::possiblyContainsTrack( const KUrl &url ) const +AggregateCollection::possiblyContainsTrack( const QUrl &url ) const { foreach( Collections::Collection *collection, m_idCollectionMap ) { if( collection->possiblyContainsTrack( url ) ) return true; } return false; } Meta::TrackPtr -AggregateCollection::trackForUrl( const KUrl &url ) +AggregateCollection::trackForUrl( const QUrl &url ) { foreach( Collections::Collection *collection, m_idCollectionMap ) { Meta::TrackPtr track = collection->trackForUrl( url ); if( track ) { //theoretically we should now query the other collections for the same track //not sure how to do that yet though... return Meta::TrackPtr( getTrack( track ) ); } } return Meta::TrackPtr(); } QueryMaker* AggregateCollection::queryMaker() { QList list; foreach( Collections::Collection *collection, m_idCollectionMap ) { list.append( collection->queryMaker() ); } return new Collections::AggregateQueryMaker( this, list ); } QString AggregateCollection::collectionId() const { // do we need more than one AggregateCollection? return QLatin1String( "AggregateCollection" ); } void AggregateCollection::addCollection( Collections::Collection *collection, CollectionManager::CollectionStatus status ) { if( !collection ) return; if( !( status & CollectionManager::CollectionViewable ) ) return; m_idCollectionMap.insert( collection->collectionId(), collection ); //TODO emit updated(); } void AggregateCollection::removeCollection( const QString &collectionId ) { m_idCollectionMap.remove( collectionId ); emit updated(); } void AggregateCollection::removeCollection( Collections::Collection *collection ) { m_idCollectionMap.remove( collection->collectionId() ); emit updated(); } void AggregateCollection::slotUpdated() { //TODO emit updated(); } void AggregateCollection::removeYear( const QString &name ) { m_yearLock.lockForWrite(); m_yearMap.remove( name ); m_yearLock.unlock(); } Meta::AggreagateYear* AggregateCollection::getYear( Meta::YearPtr year ) { m_yearLock.lockForRead(); if( m_yearMap.contains( year->name() ) ) { KSharedPtr aggregateYear = m_yearMap.value( year->name() ); aggregateYear->add( year ); m_yearLock.unlock(); return aggregateYear.data(); } else { m_yearLock.unlock(); m_yearLock.lockForWrite(); //we might create two year instances with the same name here, //which would show some weird behaviour in other places Meta::AggreagateYear *aggregateYear = new Meta::AggreagateYear( this, year ); m_yearMap.insert( year->name(), KSharedPtr( aggregateYear ) ); m_yearLock.unlock(); return aggregateYear; } } void AggregateCollection::setYear( Meta::AggreagateYear *year ) { m_yearLock.lockForWrite(); m_yearMap.insert( year->name(), KSharedPtr( year ) ); m_yearLock.unlock(); } bool AggregateCollection::hasYear( const QString &name ) { QReadLocker locker( &m_yearLock ); return m_yearMap.contains( name ); } void AggregateCollection::removeGenre( const QString &name ) { m_genreLock.lockForWrite(); m_genreMap.remove( name ); m_genreLock.unlock(); } Meta::AggregateGenre* AggregateCollection::getGenre( Meta::GenrePtr genre ) { m_genreLock.lockForRead(); if( m_genreMap.contains( genre->name() ) ) { KSharedPtr aggregateGenre = m_genreMap.value( genre->name() ); aggregateGenre->add( genre ); m_genreLock.unlock(); return aggregateGenre.data(); } else { m_genreLock.unlock(); m_genreLock.lockForWrite(); //we might create two instances with the same name here, //which would show some weird behaviour in other places Meta::AggregateGenre *aggregateGenre = new Meta::AggregateGenre( this, genre ); m_genreMap.insert( genre->name(), KSharedPtr( aggregateGenre ) ); m_genreLock.unlock(); return aggregateGenre; } } void AggregateCollection::setGenre( Meta::AggregateGenre *genre ) { m_genreLock.lockForWrite(); m_genreMap.insert( genre->name(), KSharedPtr( genre ) ); m_genreLock.unlock(); } bool AggregateCollection::hasGenre( const QString &genre ) { QReadLocker locker( &m_genreLock ); return m_genreMap.contains( genre ); } void AggregateCollection::removeComposer( const QString &name ) { m_composerLock.lockForWrite(); m_composerMap.remove( name ); m_composerLock.unlock(); } Meta::AggregateComposer* AggregateCollection::getComposer( Meta::ComposerPtr composer ) { m_composerLock.lockForRead(); if( m_composerMap.contains( composer->name() ) ) { KSharedPtr aggregateComposer = m_composerMap.value( composer->name() ); aggregateComposer->add( composer ); m_composerLock.unlock(); return aggregateComposer.data(); } else { m_composerLock.unlock(); m_composerLock.lockForWrite(); //we might create two instances with the same name here, //which would show some weird behaviour in other places Meta::AggregateComposer *aggregateComposer = new Meta::AggregateComposer( this, composer ); m_composerMap.insert( composer->name(), KSharedPtr( aggregateComposer ) ); m_composerLock.unlock(); return aggregateComposer; } } void AggregateCollection::setComposer( Meta::AggregateComposer *composer ) { m_composerLock.lockForWrite(); m_composerMap.insert( composer->name(), KSharedPtr( composer ) ); m_composerLock.unlock(); } bool AggregateCollection::hasComposer( const QString &name ) { QReadLocker locker( &m_composerLock ); return m_composerMap.contains( name ); } void AggregateCollection::removeArtist( const QString &name ) { m_artistLock.lockForWrite(); m_artistMap.remove( name ); m_artistLock.unlock(); } Meta::AggregateArtist* AggregateCollection::getArtist( Meta::ArtistPtr artist ) { m_artistLock.lockForRead(); if( m_artistMap.contains( artist->name() ) ) { KSharedPtr aggregateArtist = m_artistMap.value( artist->name() ); aggregateArtist->add( artist ); m_artistLock.unlock(); return aggregateArtist.data(); } else { m_artistLock.unlock(); m_artistLock.lockForWrite(); //we might create two instances with the same name here, //which would show some weird behaviour in other places Meta::AggregateArtist *aggregateArtist = new Meta::AggregateArtist( this, artist ); m_artistMap.insert( artist->name(), KSharedPtr( aggregateArtist ) ); m_artistLock.unlock(); return aggregateArtist; } } void AggregateCollection::setArtist( Meta::AggregateArtist *artist ) { m_artistLock.lockForWrite(); m_artistMap.insert( artist->name(), KSharedPtr( artist ) ); m_artistLock.unlock(); } bool AggregateCollection::hasArtist( const QString &artist ) { QReadLocker locker( &m_artistLock ); return m_artistMap.contains( artist ); } void AggregateCollection::removeAlbum( const QString &album, const QString &albumartist ) { Meta::AlbumKey key( album, albumartist ); m_albumLock.lockForWrite(); m_albumMap.remove( key ); m_albumLock.unlock(); } Meta::AggregateAlbum* AggregateCollection::getAlbum( Meta::AlbumPtr album ) { Meta::AlbumKey key( album ); m_albumLock.lockForRead(); if( m_albumMap.contains( key ) ) { KSharedPtr aggregateAlbum = m_albumMap.value( key ); aggregateAlbum->add( album ); m_albumLock.unlock(); return aggregateAlbum.data(); } else { m_albumLock.unlock(); m_albumLock.lockForWrite(); //we might create two instances with the same name here, //which would show some weird behaviour in other places Meta::AggregateAlbum *aggregateAlbum = new Meta::AggregateAlbum( this, album ); m_albumMap.insert( key, KSharedPtr( aggregateAlbum ) ); m_albumLock.unlock(); return aggregateAlbum; } } void AggregateCollection::setAlbum( Meta::AggregateAlbum *album ) { m_albumLock.lockForWrite(); m_albumMap.insert( Meta::AlbumKey( Meta::AlbumPtr( album ) ), KSharedPtr( album ) ); m_albumLock.unlock(); } bool AggregateCollection::hasAlbum( const QString &album, const QString &albumArtist ) { QReadLocker locker( &m_albumLock ); return m_albumMap.contains( Meta::AlbumKey( album, albumArtist ) ); } void AggregateCollection::removeTrack( const Meta::TrackKey &key ) { m_trackLock.lockForWrite(); m_trackMap.remove( key ); m_trackLock.unlock(); } Meta::AggregateTrack* AggregateCollection::getTrack( Meta::TrackPtr track ) { const Meta::TrackKey key( track ); m_trackLock.lockForRead(); if( m_trackMap.contains( key ) ) { KSharedPtr aggregateTrack = m_trackMap.value( key ); aggregateTrack->add( track ); m_trackLock.unlock(); return aggregateTrack.data(); } else { m_trackLock.unlock(); m_trackLock.lockForWrite(); //we might create two instances with the same name here, //which would show some weird behaviour in other places Meta::AggregateTrack *aggregateTrack = new Meta::AggregateTrack( this, track ); m_trackMap.insert( key, KSharedPtr( aggregateTrack ) ); m_trackLock.unlock(); return aggregateTrack; } } void AggregateCollection::setTrack( Meta::AggregateTrack *track ) { Meta::TrackPtr ptr( track ); const Meta::TrackKey key( ptr ); m_trackLock.lockForWrite(); m_trackMap.insert( key, KSharedPtr( track ) ); m_trackLock.unlock(); } bool AggregateCollection::hasTrack( const Meta::TrackKey &key ) { QReadLocker locker( &m_trackLock ); return m_trackMap.contains( key ); } bool AggregateCollection::hasLabel( const QString &name ) { QReadLocker locker( &m_labelLock ); return m_labelMap.contains( name ); } void AggregateCollection::removeLabel( const QString &name ) { QWriteLocker locker( &m_labelLock ); m_labelMap.remove( name ); } Meta::AggregateLabel* AggregateCollection::getLabel( Meta::LabelPtr label ) { m_labelLock.lockForRead(); if( m_labelMap.contains( label->name() ) ) { KSharedPtr aggregateLabel = m_labelMap.value( label->name() ); aggregateLabel->add( label ); m_labelLock.unlock(); return aggregateLabel.data(); } else { m_labelLock.unlock(); m_labelLock.lockForWrite(); //we might create two year instances with the same name here, //which would show some weird behaviour in other places Meta::AggregateLabel *aggregateLabel = new Meta::AggregateLabel( this, label ); m_labelMap.insert( label->name(), KSharedPtr( aggregateLabel ) ); m_labelLock.unlock(); return aggregateLabel; } } void AggregateCollection::setLabel( Meta::AggregateLabel *label ) { QWriteLocker locker( &m_labelLock ); m_labelMap.insert( label->name(), KSharedPtr( label ) ); } void AggregateCollection::emptyCache() { bool hasTrack, hasAlbum, hasArtist, hasYear, hasGenre, hasComposer, hasLabel; hasTrack = hasAlbum = hasArtist = hasYear = hasGenre = hasComposer = hasLabel = false; //try to avoid possible deadlocks by aborting when we can't get all locks if ( ( hasTrack = m_trackLock.tryLockForWrite() ) && ( hasAlbum = m_albumLock.tryLockForWrite() ) && ( hasArtist = m_artistLock.tryLockForWrite() ) && ( hasYear = m_yearLock.tryLockForWrite() ) && ( hasGenre = m_genreLock.tryLockForWrite() ) && ( hasComposer = m_composerLock.tryLockForWrite() ) && ( hasLabel = m_labelLock.tryLockForWrite() ) ) { //this very simple garbage collector doesn't handle cyclic object graphs //so care has to be taken to make sure that we are not dealing with a cyclic graph //by invalidating the tracks cache on all objects #define foreachInvalidateCache( Type, RealType, x ) \ for( QMutableHashIterator iter(x); iter.hasNext(); ) \ RealType::staticCast( iter.next().value() )->invalidateCache() //elem.count() == 2 is correct because elem is one pointer to the object //and the other is stored in the hash map (except for m_trackMap, where //another refence is stored in m_uidMap #define foreachCollectGarbage( Key, Type, RefCount, x ) \ for( QMutableHashIterator iter(x); iter.hasNext(); ) \ { \ Type elem = iter.next().value(); \ if( elem.count() == RefCount ) \ iter.remove(); \ } foreachCollectGarbage( Meta::TrackKey, KSharedPtr, 2, m_trackMap ) //run before artist so that album artist pointers can be garbage collected foreachCollectGarbage( Meta::AlbumKey, KSharedPtr, 2, m_albumMap ) foreachCollectGarbage( QString, KSharedPtr, 2, m_artistMap ) foreachCollectGarbage( QString, KSharedPtr, 2, m_genreMap ) foreachCollectGarbage( QString, KSharedPtr, 2, m_composerMap ) foreachCollectGarbage( QString, KSharedPtr, 2, m_yearMap ) foreachCollectGarbage( QString, KSharedPtr, 2, m_labelMap ) } //make sure to unlock all necessary locks //important: calling unlock() on an unlocked mutex gives an undefined result //unlocking a mutex locked by another thread results in an error, so be careful if( hasTrack ) m_trackLock.unlock(); if( hasAlbum ) m_albumLock.unlock(); if( hasArtist ) m_artistLock.unlock(); if( hasYear ) m_yearLock.unlock(); if( hasGenre ) m_genreLock.unlock(); if( hasComposer ) m_composerLock.unlock(); if( hasLabel ) m_labelLock.unlock(); } diff --git a/src/core-impl/collections/aggregate/AggregateCollection.h b/src/core-impl/collections/aggregate/AggregateCollection.h index 17119b6b37..7679fe0e04 100644 --- a/src/core-impl/collections/aggregate/AggregateCollection.h +++ b/src/core-impl/collections/aggregate/AggregateCollection.h @@ -1,130 +1,130 @@ /* * Copyright (c) 2009 Maximilian Kossick * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AGGREGATECOLLECTION_H #define AGGREGATECOLLECTION_H #include "core/collections/Collection.h" #include "core-impl/collections/support/CollectionManager.h" #include "core/meta/forward_declarations.h" #include "core/meta/support/MetaKeys.h" #include #include #include namespace Meta { class AggreagateYear; class AggregateTrack; class AggregateArtist; class AggregateAlbum; class AggregateGenre; class AggregateComposer; class AggregateLabel; } namespace Collections { class AMAROK_EXPORT AggregateCollection : public Collections::Collection { Q_OBJECT public: AggregateCollection(); ~AggregateCollection(); // Collections::Collection methods virtual QString prettyName() const; virtual KIcon icon() const; - virtual bool possiblyContainsTrack( const KUrl &url ) const; - virtual Meta::TrackPtr trackForUrl( const KUrl &url ); + virtual bool possiblyContainsTrack( const QUrl &url ) const; + virtual Meta::TrackPtr trackForUrl( const QUrl &url ); virtual QueryMaker* queryMaker(); virtual QString collectionId() const; // AggregateCollection methods void removeTrack( const Meta::TrackKey &key ); Meta::AggregateTrack* getTrack( Meta::TrackPtr track ); void setTrack( Meta::AggregateTrack *track ); bool hasTrack( const Meta::TrackKey &key ); void removeAlbum( const QString &album, const QString &albumArtist ); Meta::AggregateAlbum* getAlbum( Meta::AlbumPtr album ); void setAlbum( Meta::AggregateAlbum *album ); bool hasAlbum( const QString &album, const QString &albumArtist ); void removeArtist( const QString &artist ); Meta::AggregateArtist* getArtist( Meta::ArtistPtr artist ); void setArtist( Meta::AggregateArtist *artist ); bool hasArtist( const QString &artist ); void removeGenre( const QString &genre ); Meta::AggregateGenre* getGenre( Meta::GenrePtr genre ); void setGenre( Meta::AggregateGenre *genre ); bool hasGenre( const QString &genre ); void removeComposer( const QString &name ); Meta::AggregateComposer* getComposer( Meta::ComposerPtr composer ); void setComposer( Meta::AggregateComposer *composer ); bool hasComposer( const QString &name ); bool hasYear( const QString &name ); void removeYear( const QString &name ); Meta::AggreagateYear* getYear( Meta::YearPtr year ); void setYear( Meta::AggreagateYear *year ); bool hasLabel( const QString &name ); void removeLabel( const QString &name ); Meta::AggregateLabel* getLabel( Meta::LabelPtr label ); void setLabel( Meta::AggregateLabel *label ); public slots: void removeCollection( const QString &collectionId ); void removeCollection( Collections::Collection *collection ); void addCollection( Collections::Collection *collection, CollectionManager::CollectionStatus status ); void slotUpdated(); private slots: void emptyCache(); private: QHash m_idCollectionMap; QHash > m_yearMap; QHash > m_genreMap; QHash > m_composerMap; QHash > m_artistMap; QHash > m_albumMap; QHash > m_trackMap; QHash > m_labelMap; QReadWriteLock m_yearLock; QReadWriteLock m_genreLock; QReadWriteLock m_composerLock; QReadWriteLock m_artistLock; QReadWriteLock m_albumLock; QReadWriteLock m_trackLock; QReadWriteLock m_labelLock; }; } //namespace Collections #endif diff --git a/src/core-impl/collections/aggregate/AggregateMeta.cpp b/src/core-impl/collections/aggregate/AggregateMeta.cpp index 1e63d992c7..64539832f6 100644 --- a/src/core-impl/collections/aggregate/AggregateMeta.cpp +++ b/src/core-impl/collections/aggregate/AggregateMeta.cpp @@ -1,1532 +1,1532 @@ /**************************************************************************************** * Copyright (c) 2009,2010 Maximilian Kossick * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "AggregateMeta" #include "AggregateMeta.h" #include "SvgHandler.h" #include "core/meta/TrackEditor.h" #include "core/meta/support/MetaUtility.h" #include "core/support/Debug.h" #include "core-impl/collections/aggregate/AggregateCollection.h" #include #include #include namespace Meta { #define FORWARD( call ) { foreach( TrackEditorPtr e, m_editors ) { e->call; } \ if( !m_batchMode ) QTimer::singleShot( 0, m_collection, SLOT(slotUpdated()) ); } class AggregateTrackEditor : public TrackEditor { public: AggregateTrackEditor( Collections::AggregateCollection *coll, const QList &editors ) : TrackEditor() , m_batchMode( false ) , m_collection( coll ) , m_editors( editors ) {} void beginUpdate() { m_batchMode = true; foreach( TrackEditorPtr ec, m_editors ) ec->beginUpdate(); } void endUpdate() { foreach( TrackEditorPtr ec, m_editors ) ec->endUpdate(); m_batchMode = false; QTimer::singleShot( 0, m_collection, SLOT(slotUpdated()) ); } void setComment( const QString &newComment ) { FORWARD( setComment( newComment ) ) } void setTrackNumber( int newTrackNumber ) { FORWARD( setTrackNumber( newTrackNumber ) ) } void setDiscNumber( int newDiscNumber ) { FORWARD( setDiscNumber( newDiscNumber ) ) } void setBpm( const qreal newBpm ) { FORWARD( setBpm( newBpm ) ) } void setTitle( const QString &newTitle ) { FORWARD( setTitle( newTitle ) ) } void setArtist( const QString &newArtist ) { FORWARD( setArtist( newArtist ) ) } void setAlbum( const QString &newAlbum ) { FORWARD( setAlbum( newAlbum ) ) } void setAlbumArtist( const QString &newAlbumArtist ) { FORWARD( setAlbumArtist ( newAlbumArtist ) ) } void setGenre( const QString &newGenre ) { FORWARD( setGenre( newGenre ) ) } void setComposer( const QString &newComposer ) { FORWARD( setComposer( newComposer ) ) } void setYear( int newYear ) { FORWARD( setYear( newYear ) ) } private: bool m_batchMode; Collections::AggregateCollection *m_collection; QList m_editors; }; #undef FORWARD AggregateTrack::AggregateTrack( Collections::AggregateCollection *coll, const TrackPtr &track ) : Track() , Observer() , m_collection( coll ) , m_name( track->name() ) , m_album( 0 ) , m_artist( 0 ) , m_genre( 0 ) , m_composer( 0 ) , m_year( 0 ) { subscribeTo( track ); m_tracks.append( track ); if( track->album() ) m_album = Meta::AlbumPtr( m_collection->getAlbum( track->album() ) ); if( track->artist() ) m_artist = Meta::ArtistPtr( m_collection->getArtist( track->artist() ) ); if( track->genre() ) m_genre = Meta::GenrePtr( m_collection->getGenre( track->genre() ) ); if( track->composer() ) m_composer = Meta::ComposerPtr( m_collection->getComposer( track->composer() ) ); if( track->year() ) m_year = Meta::YearPtr( m_collection->getYear( track->year() ) ); } AggregateTrack::~AggregateTrack() { } QString AggregateTrack::name() const { return m_name; } QString AggregateTrack::prettyName() const { return m_name; } QString AggregateTrack::sortableName() const { if( !m_tracks.isEmpty() ) return m_tracks.first()->sortableName(); return m_name; } -KUrl +QUrl AggregateTrack::playableUrl() const { Meta::TrackPtr bestPlayableTrack; foreach( const Meta::TrackPtr &track, m_tracks ) { if( track->isPlayable() ) { bool local = track->playableUrl().isLocalFile(); if( local ) { bestPlayableTrack = track; break; } else { //we might want to add some more sophisticated logic to figure out //the best remote track to play, but this works for now bestPlayableTrack = track; } } } if( bestPlayableTrack ) return bestPlayableTrack->playableUrl(); - return KUrl(); + return QUrl(); } QString AggregateTrack::prettyUrl() const { if( m_tracks.count() == 1 ) { return m_tracks.first()->prettyUrl(); } else { return QString(); } } QString AggregateTrack::uidUrl() const { // this is where it gets interesting // a uidUrl for a AggregateTrack probably has to be generated // from the parts of the key in AggregateCollection // need to think about this some more return QString(); } QString AggregateTrack::notPlayableReason() const { QStringList reasons; foreach( const Meta::TrackPtr &track, m_tracks ) { if( !track->isPlayable() ) reasons.append( track->notPlayableReason() ); else return QString(); // no reason if at least one playable } return reasons.join( QString( ", " ) ); } Meta::AlbumPtr AggregateTrack::album() const { return m_album; } Meta::ArtistPtr AggregateTrack::artist() const { return m_artist; } Meta::ComposerPtr AggregateTrack::composer() const { return m_composer; } Meta::GenrePtr AggregateTrack::genre() const { return m_genre; } Meta::YearPtr AggregateTrack::year() const { return m_year; } QString AggregateTrack::comment() const { //try to return something sensible here... //do not show a comment if the internal tracks disagree about the comment QString comment; if( !m_tracks.isEmpty() ) comment = m_tracks.first()->comment(); foreach( const Meta::TrackPtr &track, m_tracks ) { if( track->comment() != comment ) { comment.clear(); break; } } return comment; } qreal AggregateTrack::bpm() const { //Similar to comment(), try to return something sensible here... //do not show a common bpm value if the internal tracks disagree about the bpm qreal bpm = -1.0; if( !m_tracks.isEmpty() ) bpm = m_tracks.first()->bpm(); foreach( const Meta::TrackPtr &track, m_tracks ) { if( track->bpm() != bpm ) { bpm = -1.0; break; } } return bpm; } double AggregateTrack::score() const { //again, multiple ways to implement this method: //return the maximum score, the minimum score, the average //the score of the track with the maximum play count, //or an average weighted by play count. And probably a couple of ways that //I cannot think of right now... //implementing the weighted average here... double weightedSum = 0.0; int totalCount = 0; foreach( const Meta::TrackPtr &track, m_tracks ) { ConstStatisticsPtr statistics = track->statistics(); totalCount += statistics->playCount(); weightedSum += statistics->playCount() * statistics->score(); } if( totalCount ) return weightedSum / totalCount; return 0.0; } void AggregateTrack::setScore( double newScore ) { foreach( Meta::TrackPtr track, m_tracks ) { track->statistics()->setScore( newScore ); } } int AggregateTrack::rating() const { //yay, multiple options again. As this has to be defined by the user, let's take //the maximum here. int result = 0; foreach( const Meta::TrackPtr &track, m_tracks ) { if( track->statistics()->rating() > result ) result = track->statistics()->rating(); } return result; } void AggregateTrack::setRating( int newRating ) { foreach( Meta::TrackPtr track, m_tracks ) { track->statistics()->setRating( newRating ); } } QDateTime AggregateTrack::firstPlayed() const { QDateTime result; foreach( const Meta::TrackPtr &track, m_tracks ) { ConstStatisticsPtr statistics = track->statistics(); //use the track's firstPlayed value if it represents an earlier timestamp than //the current result, or use it directly if result has not been set yet //this should result in the earliest timestamp for first play of all internal //tracks being returned if( ( statistics->firstPlayed().isValid() && result.isValid() && statistics->firstPlayed() < result ) || ( statistics->firstPlayed().isValid() && !result.isValid() ) ) { result = statistics->firstPlayed(); } } return result; } void AggregateTrack::setFirstPlayed( const QDateTime &date ) { foreach( Meta::TrackPtr track, m_tracks ) { // only "lower" the first played Meta::StatisticsPtr trackStats = track->statistics(); if( !trackStats->firstPlayed().isValid() || trackStats->firstPlayed() > date ) { trackStats->setFirstPlayed( date ); } } } QDateTime AggregateTrack::lastPlayed() const { QDateTime result; //return the latest timestamp. Easier than firstPlayed because we do not have to //care about 0. //when are we going to perform the refactoring as discussed in Berlin? foreach( const Meta::TrackPtr &track, m_tracks ) { if( track->statistics()->lastPlayed() > result ) { result = track->statistics()->lastPlayed(); } } return result; } void AggregateTrack::setLastPlayed(const QDateTime& date) { foreach( Meta::TrackPtr track, m_tracks ) { // only "raise" the last played Meta::StatisticsPtr trackStats = track->statistics(); if( !trackStats->lastPlayed().isValid() || trackStats->lastPlayed() < date ) { trackStats->setLastPlayed( date ); } } } int AggregateTrack::playCount() const { // show the maximum of all play counts. int result = 0; foreach( const Meta::TrackPtr &track, m_tracks ) { if( track->statistics()->playCount() > result ) { result = track->statistics()->playCount(); } } return result; } void AggregateTrack::setPlayCount( int newPlayCount ) { Q_UNUSED( newPlayCount ) // no safe thing to do here. Notice we override finishedPlaying() } void AggregateTrack::finishedPlaying( double playedFraction ) { foreach( Meta::TrackPtr track, m_tracks ) { track->finishedPlaying( playedFraction ); } } qint64 AggregateTrack::length() const { foreach( const Meta::TrackPtr &track, m_tracks ) { if( track->length() ) return track->length(); } return 0; } int AggregateTrack::filesize() const { foreach( const Meta::TrackPtr &track, m_tracks ) { if( track->filesize() ) { return track->filesize(); } } return 0; } int AggregateTrack::sampleRate() const { foreach( const Meta::TrackPtr &track, m_tracks ) { if( track->sampleRate() ) return track->sampleRate(); } return 0; } int AggregateTrack::bitrate() const { foreach( const Meta::TrackPtr &track, m_tracks ) { if( track->bitrate() ) return track->bitrate(); } return 0; } QDateTime AggregateTrack::createDate() const { QDateTime result; foreach( const Meta::TrackPtr &track, m_tracks ) { //use the track's firstPlayed value if it represents an earlier timestamp than //the current result, or use it directly if result has not been set yet //this should result in the earliest timestamp for first play of all internal //tracks being returned if( ( track->createDate().isValid() && result.isValid() && track->createDate() < result ) || ( track->createDate().isValid() && !result.isValid() ) ) { result = track->createDate(); } } return result; } int AggregateTrack::trackNumber() const { int result = 0; foreach( const Meta::TrackPtr &track, m_tracks ) { if( ( !result && track->trackNumber() ) || ( result && result == track->trackNumber() ) ) { result = track->trackNumber(); } else if( result && result != track->trackNumber() ) { //tracks disagree about the tracknumber return 0; } } return result; } int AggregateTrack::discNumber() const { int result = 0; foreach( const Meta::TrackPtr &track, m_tracks ) { if( ( !result && track->discNumber() ) || ( result && result == track->discNumber() ) ) { result = track->discNumber(); } else if( result && result != track->discNumber() ) { //tracks disagree about the disc number return 0; } } return result; } QString AggregateTrack::type() const { if( m_tracks.size() == 1 ) { return m_tracks.first()->type(); } else { //TODO: figure something out return QString(); } } Collections::Collection* AggregateTrack::collection() const { return m_collection; } bool AggregateTrack::hasCapabilityInterface( Capabilities::Capability::Type type ) const { if( m_tracks.count() == 1 ) // if we aggregate only one track, simply return the tracks capability directly return m_tracks.first()->hasCapabilityInterface( type ); else return false; } Capabilities::Capability* AggregateTrack::createCapabilityInterface( Capabilities::Capability::Type type ) { if( m_tracks.count() == 1 ) return m_tracks.first()->createCapabilityInterface( type ); else return 0; } TrackEditorPtr AggregateTrack::editor() { if( m_tracks.count() == 1 ) return m_tracks.first()->editor(); QList editors; foreach( Meta::TrackPtr track, m_tracks ) { Meta::TrackEditorPtr ec = track->editor(); if( ec ) editors << ec; else return TrackEditorPtr(); } return TrackEditorPtr( new AggregateTrackEditor( m_collection, editors ) ); } void AggregateTrack::addLabel( const QString &label ) { foreach( Meta::TrackPtr track, m_tracks ) { track->addLabel( label ); } } void AggregateTrack::addLabel( const Meta::LabelPtr &label ) { foreach( Meta::TrackPtr track, m_tracks ) { track->addLabel( label ); } } void AggregateTrack::removeLabel( const Meta::LabelPtr &label ) { foreach( Meta::TrackPtr track, m_tracks ) { track->removeLabel( label ); } } Meta::LabelList AggregateTrack::labels() const { QSet aggregateLabels; foreach( const Meta::TrackPtr &track, m_tracks ) { foreach( Meta::LabelPtr label, track->labels() ) { aggregateLabels.insert( m_collection->getLabel( label ) ); } } Meta::LabelList result; foreach( AggregateLabel *label, aggregateLabels ) { result << Meta::LabelPtr( label ); } return result; } StatisticsPtr AggregateTrack::statistics() { return StatisticsPtr( this ); } void AggregateTrack::add( const Meta::TrackPtr &track ) { if( !track || m_tracks.contains( track ) ) return; m_tracks.append( track ); subscribeTo( track ); notifyObservers(); } void AggregateTrack::metadataChanged( Meta::TrackPtr track ) { if( !track ) return; if( !m_tracks.contains( track ) ) { //why are we subscribed? unsubscribeFrom( track ); return; } const TrackKey myKey( Meta::TrackPtr( this ) ); const TrackKey otherKey( track ); if( myKey == otherKey ) { //no key relevant metadata did change notifyObservers(); return; } else { if( m_tracks.size() == 1 ) { if( m_collection->hasTrack( otherKey ) ) { unsubscribeFrom( track ); m_collection->getTrack( track ); m_tracks.removeAll( track ); m_collection->removeTrack( myKey ); return; //do not notify observers, this track is not valid anymore! } else { m_name = track->name(); if( track->album() ) m_album = Meta::AlbumPtr( m_collection->getAlbum( track->album() ) ); if( track->artist() ) m_artist = Meta::ArtistPtr( m_collection->getArtist( track->artist() ) ); if( track->genre() ) m_genre = Meta::GenrePtr( m_collection->getGenre( track->genre() ) ); if( track->composer() ) m_composer = Meta::ComposerPtr( m_collection->getComposer( track->composer() ) ); if( track->year() ) m_year = Meta::YearPtr( m_collection->getYear( track->year() ) ); m_collection->setTrack( this ); m_collection->removeTrack( myKey ); } } else { unsubscribeFrom( track ); m_collection->getTrack( track ); m_tracks.removeAll( track ); } notifyObservers(); } } AggregateAlbum::AggregateAlbum( Collections::AggregateCollection *coll, Meta::AlbumPtr album ) : Meta::Album() , Meta::Observer() , m_collection( coll ) , m_name( album->name() ) { m_albums.append( album ); if( album->hasAlbumArtist() ) m_albumArtist = Meta::ArtistPtr( m_collection->getArtist( album->albumArtist() ) ); } AggregateAlbum::~AggregateAlbum() { } QString AggregateAlbum::name() const { return m_name; } QString AggregateAlbum::prettyName() const { return m_name; } QString AggregateAlbum::sortableName() const { if( !m_albums.isEmpty() ) return m_albums.first()->sortableName(); return m_name; } Meta::TrackList AggregateAlbum::tracks() { QSet tracks; foreach( Meta::AlbumPtr album, m_albums ) { Meta::TrackList tmp = album->tracks(); foreach( const Meta::TrackPtr &track, tmp ) { tracks.insert( m_collection->getTrack( track ) ); } } Meta::TrackList result; foreach( AggregateTrack *track, tracks ) { result.append( Meta::TrackPtr( track ) ); } return result; } Meta::ArtistPtr AggregateAlbum::albumArtist() const { return m_albumArtist; } bool AggregateAlbum::isCompilation() const { return m_albumArtist.isNull(); } bool AggregateAlbum::hasAlbumArtist() const { return !m_albumArtist.isNull(); } bool AggregateAlbum::hasCapabilityInterface(Capabilities::Capability::Type type ) const { if( m_albums.count() == 1 ) { return m_albums.first()->hasCapabilityInterface( type ); } else { return false; } } Capabilities::Capability* AggregateAlbum::createCapabilityInterface( Capabilities::Capability::Type type ) { if( m_albums.count() == 1 ) { return m_albums.first()->createCapabilityInterface( type ); } else { return 0; } } void AggregateAlbum::add( Meta::AlbumPtr album ) { if( !album || m_albums.contains( album ) ) return; m_albums.append( album ); subscribeTo( album ); notifyObservers(); } bool AggregateAlbum::hasImage( int size ) const { foreach( const Meta::AlbumPtr &album, m_albums ) { if( album->hasImage( size ) ) return true; } return false; } QImage AggregateAlbum::image( int size ) const { foreach( Meta::AlbumPtr album, m_albums ) { if( album->hasImage( size ) ) { return album->image( size ); } } return Meta::Album::image( size ); } -KUrl +QUrl AggregateAlbum::imageLocation( int size ) { foreach( Meta::AlbumPtr album, m_albums ) { if( album->hasImage( size ) ) { - KUrl url = album->imageLocation( size ); + QUrl url = album->imageLocation( size ); if( url.isValid() ) { return url; } } } - return KUrl(); + return QUrl(); } QPixmap AggregateAlbum::imageWithBorder( int size, int borderWidth ) { foreach( Meta::AlbumPtr album, m_albums ) { if( album->hasImage( size ) ) { return The::svgHandler()->imageWithBorder( album, size, borderWidth ); } } return QPixmap(); } bool AggregateAlbum::canUpdateImage() const { if( m_albums.count() == 0 ) return false; foreach( const Meta::AlbumPtr &album, m_albums ) { //we can only update the image for all albusm at the same time if( !album->canUpdateImage() ) return false; } return true; } void AggregateAlbum::setImage( const QImage &image ) { foreach( Meta::AlbumPtr album, m_albums ) { album->setImage( image ); } } void AggregateAlbum::removeImage() { foreach( Meta::AlbumPtr album, m_albums ) { album->removeImage(); } } void AggregateAlbum::setSuppressImageAutoFetch( bool suppress ) { foreach( Meta::AlbumPtr album, m_albums ) { album->setSuppressImageAutoFetch( suppress ); } } bool AggregateAlbum::suppressImageAutoFetch() const { foreach( const Meta::AlbumPtr &album, m_albums ) { if( !album->suppressImageAutoFetch() ) return false; } return true; } void AggregateAlbum::metadataChanged( Meta::AlbumPtr album ) { if( !album || !m_albums.contains( album ) ) return; if( album->name() != m_name || hasAlbumArtist() != album->hasAlbumArtist() || ( hasAlbumArtist() && m_albumArtist->name() != album->albumArtist()->name() ) ) { if( m_albums.count() > 1 ) { m_collection->getAlbum( album ); unsubscribeFrom( album ); m_albums.removeAll( album ); } else { Meta::ArtistPtr albumartist; if( album->hasAlbumArtist() ) albumartist = Meta::ArtistPtr( m_collection->getArtist( album->albumArtist() ) ); QString artistname = m_albumArtist ? m_albumArtist->name() : QString(); m_collection->removeAlbum( m_name, artistname ); m_name = album->name(); m_albumArtist = albumartist; m_collection->setAlbum( this ); } } notifyObservers(); } AggregateArtist::AggregateArtist( Collections::AggregateCollection *coll, Meta::ArtistPtr artist ) : Meta::Artist() , Meta::Observer() , m_collection( coll ) , m_name( artist->name() ) { m_artists.append( artist ); subscribeTo( artist ); } AggregateArtist::~AggregateArtist() { } QString AggregateArtist::name() const { return m_name; } QString AggregateArtist::prettyName() const { return m_name; } QString AggregateArtist::sortableName() const { if( !m_artists.isEmpty() ) return m_artists.first()->sortableName(); return m_name; } Meta::TrackList AggregateArtist::tracks() { QSet tracks; foreach( Meta::ArtistPtr artist, m_artists ) { Meta::TrackList tmp = artist->tracks(); foreach( const Meta::TrackPtr &track, tmp ) { tracks.insert( m_collection->getTrack( track ) ); } } Meta::TrackList result; foreach( AggregateTrack *track, tracks ) { result.append( Meta::TrackPtr( track ) ); } return result; } bool AggregateArtist::hasCapabilityInterface(Capabilities::Capability::Type type ) const { if( m_artists.count() == 1 ) { return m_artists.first()->hasCapabilityInterface( type ); } else { return false; } } Capabilities::Capability* AggregateArtist::createCapabilityInterface( Capabilities::Capability::Type type ) { if( m_artists.count() == 1 ) { return m_artists.first()->createCapabilityInterface( type ); } else { return 0; } } void AggregateArtist::add( Meta::ArtistPtr artist ) { if( !artist || m_artists.contains( artist ) ) return; m_artists.append( artist ); subscribeTo( artist ); notifyObservers(); } void AggregateArtist::metadataChanged( Meta::ArtistPtr artist ) { if( !artist || !m_artists.contains( artist ) ) return; if( artist->name() != m_name ) { if( m_artists.count() > 1 ) { m_collection->getArtist( artist ); unsubscribeFrom( artist ); m_artists.removeAll( artist ); } else { //possible race condition here: //if another thread creates an Artist with the new name //we will have two instances that have the same name! //TODO: figure out a way around that //the race condition is a problem for all other metadataChanged methods too m_collection->removeArtist( m_name ); m_name = artist->name(); m_collection->setArtist( this ); } } notifyObservers(); } AggregateGenre::AggregateGenre( Collections::AggregateCollection *coll, Meta::GenrePtr genre ) : Meta::Genre() , Meta::Observer() , m_collection( coll ) , m_name( genre->name() ) { m_genres.append( genre ); subscribeTo( genre ); } AggregateGenre::~AggregateGenre() { } QString AggregateGenre::name() const { return m_name; } QString AggregateGenre::prettyName() const { return m_name; } QString AggregateGenre::sortableName() const { if( !m_genres.isEmpty() ) return m_genres.first()->sortableName(); return m_name; } Meta::TrackList AggregateGenre::tracks() { QSet tracks; foreach( Meta::GenrePtr genre, m_genres ) { Meta::TrackList tmp = genre->tracks(); foreach( const Meta::TrackPtr &track, tmp ) { tracks.insert( m_collection->getTrack( track ) ); } } Meta::TrackList result; foreach( AggregateTrack *track, tracks ) { result.append( Meta::TrackPtr( track ) ); } return result; } bool AggregateGenre::hasCapabilityInterface(Capabilities::Capability::Type type ) const { if( m_genres.count() == 1 ) { return m_genres.first()->hasCapabilityInterface( type ); } else { return false; } } Capabilities::Capability* AggregateGenre::createCapabilityInterface( Capabilities::Capability::Type type ) { if( m_genres.count() == 1 ) { return m_genres.first()->createCapabilityInterface( type ); } else { return 0; } } void AggregateGenre::add( Meta::GenrePtr genre ) { if( !genre || m_genres.contains( genre ) ) return; m_genres.append( genre ); subscribeTo( genre ); notifyObservers(); } void AggregateGenre::metadataChanged( Meta::GenrePtr genre ) { if( !genre || !m_genres.contains( genre ) ) return; if( genre->name() != m_name ) { if( m_genres.count() > 1 ) { m_collection->getGenre( genre ); unsubscribeFrom( genre ); m_genres.removeAll( genre ); } else { m_collection->removeGenre( m_name ); m_collection->setGenre( this ); m_name = genre->name(); } } notifyObservers(); } AggregateComposer::AggregateComposer( Collections::AggregateCollection *coll, Meta::ComposerPtr composer ) : Meta::Composer() , Meta::Observer() , m_collection( coll ) , m_name( composer->name() ) { m_composers.append( composer ); subscribeTo( composer ); } AggregateComposer::~AggregateComposer() { } QString AggregateComposer::name() const { return m_name; } QString AggregateComposer::prettyName() const { return m_name; } QString AggregateComposer::sortableName() const { if( !m_composers.isEmpty() ) return m_composers.first()->sortableName(); return m_name; } Meta::TrackList AggregateComposer::tracks() { QSet tracks; foreach( Meta::ComposerPtr composer, m_composers ) { Meta::TrackList tmp = composer->tracks(); foreach( const Meta::TrackPtr &track, tmp ) { tracks.insert( m_collection->getTrack( track ) ); } } Meta::TrackList result; foreach( AggregateTrack *track, tracks ) { result.append( Meta::TrackPtr( track ) ); } return result; } bool AggregateComposer::hasCapabilityInterface(Capabilities::Capability::Type type ) const { if( m_composers.count() == 1 ) { return m_composers.first()->hasCapabilityInterface( type ); } else { return false; } } Capabilities::Capability* AggregateComposer::createCapabilityInterface( Capabilities::Capability::Type type ) { if( m_composers.count() == 1 ) { return m_composers.first()->createCapabilityInterface( type ); } else { return 0; } } void AggregateComposer::add( Meta::ComposerPtr composer ) { if( !composer || m_composers.contains( composer ) ) return; m_composers.append( composer ); subscribeTo( composer ); notifyObservers(); } void AggregateComposer::metadataChanged( Meta::ComposerPtr composer ) { if( !composer || !m_composers.contains( composer ) ) return; if( composer->name() != m_name ) { if( m_composers.count() > 1 ) { m_collection->getComposer( composer ); unsubscribeFrom( composer ); m_composers.removeAll( composer ); } else { m_collection->removeComposer( m_name ); m_collection->setComposer( this ); m_name = composer->name(); } } notifyObservers(); } AggreagateYear::AggreagateYear( Collections::AggregateCollection *coll, Meta::YearPtr year ) : Meta::Year() , Meta::Observer() , m_collection( coll ) , m_name( year->name() ) { m_years.append( year ); subscribeTo( year ); } AggreagateYear::~AggreagateYear() { //nothing to do } QString AggreagateYear::name() const { return m_name; } QString AggreagateYear::prettyName() const { return m_name; } QString AggreagateYear::sortableName() const { if( !m_years.isEmpty() ) return m_years.first()->sortableName(); return m_name; } Meta::TrackList AggreagateYear::tracks() { QSet tracks; foreach( Meta::YearPtr year, m_years ) { Meta::TrackList tmp = year->tracks(); foreach( const Meta::TrackPtr &track, tmp ) { tracks.insert( m_collection->getTrack( track ) ); } } Meta::TrackList result; foreach( AggregateTrack *track, tracks ) { result.append( Meta::TrackPtr( track ) ); } return result; } bool AggreagateYear::hasCapabilityInterface(Capabilities::Capability::Type type ) const { if( m_years.count() == 1 ) { return m_years.first()->hasCapabilityInterface( type ); } else { return false; } } Capabilities::Capability* AggreagateYear::createCapabilityInterface( Capabilities::Capability::Type type ) { if( m_years.count() == 1 ) { return m_years.first()->createCapabilityInterface( type ); } else { return 0; } } void AggreagateYear::add( Meta::YearPtr year ) { if( !year || m_years.contains( year ) ) return; m_years.append( year ); subscribeTo( year ); notifyObservers(); } void AggreagateYear::metadataChanged( Meta::YearPtr year ) { if( !year || !m_years.contains( year ) ) return; if( year->name() != m_name ) { if( m_years.count() > 1 ) { m_collection->getYear( year ); unsubscribeFrom( year ); m_years.removeAll( year ); } else { if( m_collection->hasYear( year->name() ) ) { unsubscribeFrom( year ); m_collection->getYear( year ); m_years.removeAll( year ); m_collection->removeYear( m_name ); return; //do NOT notify observers, the instance is not valid anymore! } else { // be careful with the ordering of instructions here // AggregateCollection uses KSharedPtr internally // so we have to make sure that there is more than one pointer // to this instance by registering this instance under the new name // before removing the old one. Otherwise kSharedPtr might delete this // instance in removeYear() QString tmpName = m_name; m_name = year->name(); m_collection->setYear( this ); m_collection->removeYear( tmpName ); } } } notifyObservers(); } AggregateLabel::AggregateLabel( Collections::AggregateCollection *coll, const Meta::LabelPtr &label ) : Meta::Label() , m_collection( coll ) , m_name( label->name() ) { m_labels.append( label ); Q_UNUSED(m_collection); // might be needed later } AggregateLabel::~AggregateLabel() { //nothing to do } QString AggregateLabel::name() const { return m_name; } QString AggregateLabel::prettyName() const { return m_name; } QString AggregateLabel::sortableName() const { if( !m_labels.isEmpty() ) return m_labels.first()->sortableName(); return m_name; } bool AggregateLabel::hasCapabilityInterface( Capabilities::Capability::Type type ) const { if( m_labels.count() == 1 ) { return m_labels.first()->hasCapabilityInterface( type ); } else { return false; } } Capabilities::Capability* AggregateLabel::createCapabilityInterface( Capabilities::Capability::Type type ) { if( m_labels.count() == 1 ) { return m_labels.first()->createCapabilityInterface( type ); } else { return 0; } } void AggregateLabel::add( const Meta::LabelPtr &label ) { if( !label || m_labels.contains( label ) ) return; m_labels.append( label ); } } //namespace Meta diff --git a/src/core-impl/collections/aggregate/AggregateMeta.h b/src/core-impl/collections/aggregate/AggregateMeta.h index 4c19bdc580..7a2b02cecf 100644 --- a/src/core-impl/collections/aggregate/AggregateMeta.h +++ b/src/core-impl/collections/aggregate/AggregateMeta.h @@ -1,301 +1,301 @@ /**************************************************************************************** * Copyright (c) 2009 Maximilian Kossick * * * * 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, see . * ****************************************************************************************/ #ifndef AGGREGATEMETA_H #define AGGREGATEMETA_H #include "amarok_export.h" #include "core/meta/Meta.h" #include "core/meta/Observer.h" #include "core/meta/Statistics.h" #include namespace Collections { class AggregateCollection; } namespace Meta { class AMAROK_EXPORT AggregateTrack : public Meta::Track, public Meta::Statistics, private Meta::Observer { public: AggregateTrack( Collections::AggregateCollection *coll, const Meta::TrackPtr &track ); ~AggregateTrack(); QString name() const; QString prettyName() const; virtual QString sortableName() const; - KUrl playableUrl() const; + QUrl playableUrl() const; QString prettyUrl() const; QString uidUrl() const; /** * Return a comma separated list of reasons why constituent * tracks are unplayable or an empty string if any of the tracks is playable */ QString notPlayableReason() const; Meta::AlbumPtr album() const; Meta::ArtistPtr artist() const; Meta::ComposerPtr composer() const; Meta::GenrePtr genre() const; Meta::YearPtr year() const; QString comment() const; qreal bpm() const; void finishedPlaying( double playedFraction ); qint64 length() const; int filesize() const; int sampleRate() const; int bitrate() const; QDateTime createDate() const; int trackNumber() const; int discNumber() const; QString type() const; Collections::Collection* collection() const; virtual bool hasCapabilityInterface( Capabilities::Capability::Type type ) const; virtual Capabilities::Capability* createCapabilityInterface( Capabilities::Capability::Type type ); virtual void addLabel( const QString &label ); virtual void addLabel( const Meta::LabelPtr &label ); virtual void removeLabel( const Meta::LabelPtr &label ); virtual Meta::LabelList labels() const; virtual TrackEditorPtr editor(); virtual StatisticsPtr statistics(); // Meta::Statistics methods: double score() const; void setScore( double newScore ); int rating() const; void setRating( int newRating ); QDateTime firstPlayed() const; void setFirstPlayed( const QDateTime &date ); QDateTime lastPlayed() const; void setLastPlayed( const QDateTime &date ); int playCount() const; void setPlayCount( int newPlayCount ); void add( const Meta::TrackPtr &track ); protected: using Observer::metadataChanged; virtual void metadataChanged( Meta::TrackPtr track ); private: Collections::AggregateCollection *m_collection; Meta::TrackList m_tracks; QString m_name; Meta::AlbumPtr m_album; Meta::ArtistPtr m_artist; Meta::GenrePtr m_genre; Meta::ComposerPtr m_composer; Meta::YearPtr m_year; }; class AMAROK_EXPORT AggregateAlbum : public Meta::Album, private Meta::Observer { public: AggregateAlbum( Collections::AggregateCollection *coll, Meta::AlbumPtr album ); ~AggregateAlbum(); QString name() const; QString prettyName() const; virtual QString sortableName() const; Meta::TrackList tracks(); Meta::ArtistPtr albumArtist() const; bool isCompilation() const; bool hasAlbumArtist() const; virtual bool hasCapabilityInterface( Capabilities::Capability::Type type ) const; virtual Capabilities::Capability* createCapabilityInterface( Capabilities::Capability::Type type ); void add( Meta::AlbumPtr album ); /** returns true if the album has a cover set */ virtual bool hasImage( int size = 0 ) const; /** returns the cover of the album */ virtual QImage image( int size = 0 ) const; /** returns the image location on disk */ - virtual KUrl imageLocation( int size = 0 ); + virtual QUrl imageLocation( int size = 0 ); /** returns the cover of the album with a nice border around it*/ virtual QPixmap imageWithBorder( int size = 0, int borderWidth = 5 ); /** Returns true if it is possible to update the cover of the album */ virtual bool canUpdateImage() const; /** updates the cover of the album */ virtual void setImage( const QImage &image ); virtual void removeImage(); /** don't automatically fetch artwork */ virtual void setSuppressImageAutoFetch( const bool suppress ); /** should automatic artwork retrieval be suppressed? */ virtual bool suppressImageAutoFetch() const; protected: using Observer::metadataChanged; virtual void metadataChanged( Meta::AlbumPtr album ); private: Collections::AggregateCollection *m_collection; Meta::AlbumList m_albums; QString m_name; Meta::ArtistPtr m_albumArtist; }; class AMAROK_EXPORT AggregateArtist : public Meta::Artist, private Meta::Observer { public: AggregateArtist( Collections::AggregateCollection *coll, Meta::ArtistPtr artist ); ~AggregateArtist(); QString name() const; QString prettyName() const; virtual QString sortableName() const; Meta::TrackList tracks(); virtual bool hasCapabilityInterface( Capabilities::Capability::Type type ) const; virtual Capabilities::Capability* createCapabilityInterface( Capabilities::Capability::Type type ); void add( Meta::ArtistPtr artist ); protected: using Observer::metadataChanged; virtual void metadataChanged( Meta::ArtistPtr artist ); private: Collections::AggregateCollection *m_collection; Meta::ArtistList m_artists; QString m_name; }; class AMAROK_EXPORT AggregateGenre : public Meta::Genre, private Meta::Observer { public: AggregateGenre( Collections::AggregateCollection *coll, Meta::GenrePtr genre ); ~AggregateGenre(); QString name() const; QString prettyName() const; virtual QString sortableName() const; Meta::TrackList tracks(); virtual bool hasCapabilityInterface( Capabilities::Capability::Type type ) const; virtual Capabilities::Capability* createCapabilityInterface( Capabilities::Capability::Type type ); void add( Meta::GenrePtr genre ); protected: using Observer::metadataChanged; virtual void metadataChanged( Meta::GenrePtr genre ); private: Collections::AggregateCollection *m_collection; Meta::GenreList m_genres; QString m_name; }; class AMAROK_EXPORT AggregateComposer : public Meta::Composer, private Meta::Observer { public: AggregateComposer( Collections::AggregateCollection *coll, Meta::ComposerPtr composer ); ~AggregateComposer(); QString name() const; QString prettyName() const; virtual QString sortableName() const; Meta::TrackList tracks(); virtual bool hasCapabilityInterface( Capabilities::Capability::Type type ) const; virtual Capabilities::Capability* createCapabilityInterface( Capabilities::Capability::Type type ); void add( Meta::ComposerPtr composer ); protected: using Observer::metadataChanged; virtual void metadataChanged( Meta::ComposerPtr composer ); private: Collections::AggregateCollection *m_collection; Meta::ComposerList m_composers; QString m_name; }; class AMAROK_EXPORT AggreagateYear : public Meta::Year, private Meta::Observer { public: AggreagateYear( Collections::AggregateCollection * coll, Meta::YearPtr year ); ~AggreagateYear(); QString name() const; QString prettyName() const; virtual QString sortableName() const; Meta::TrackList tracks(); virtual bool hasCapabilityInterface( Capabilities::Capability::Type type ) const; virtual Capabilities::Capability* createCapabilityInterface( Capabilities::Capability::Type type ); /** * adds another Meta::Year instance to be proxied. */ void add( Meta::YearPtr year ); protected: using Observer::metadataChanged; virtual void metadataChanged( Meta::YearPtr year ); private: Collections::AggregateCollection *m_collection; Meta::YearList m_years; QString m_name; }; class AMAROK_EXPORT AggregateLabel : public Meta::Label { public: AggregateLabel( Collections::AggregateCollection *coll, const Meta::LabelPtr &label ); virtual ~AggregateLabel(); virtual QString name() const; virtual QString prettyName() const; virtual QString sortableName() const; virtual bool hasCapabilityInterface( Capabilities::Capability::Type type ) const; virtual Capabilities::Capability* createCapabilityInterface( Capabilities::Capability::Type type ); /** adds another Meta::Label instance to be proxied. */ void add( const Meta::LabelPtr &label ); private: Collections::AggregateCollection *m_collection; Meta::LabelList m_labels; QString m_name; }; } // namespace Meta #endif // AGGREGATEMETA_H diff --git a/src/core-impl/collections/audiocd/AudioCdCollection.cpp b/src/core-impl/collections/audiocd/AudioCdCollection.cpp index 3dfa7c3b07..e2e75508e4 100644 --- a/src/core-impl/collections/audiocd/AudioCdCollection.cpp +++ b/src/core-impl/collections/audiocd/AudioCdCollection.cpp @@ -1,611 +1,612 @@ /**************************************************************************************** * Copyright (c) 2009 Nikolaj Hald Nielsen * * Copyright (c) 2009 Seb Ruiz * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "AudioCdCollection" #include "AudioCdCollection.h" #include "MainWindow.h" #include "amarokconfig.h" #include "AudioCdCollectionLocation.h" #include "AudioCdMeta.h" #include "core-impl/collections/support/CollectionManager.h" #include "core-impl/collections/support/MemoryQueryMaker.h" #include "covermanager/CoverFetcher.h" #include "core/support/Debug.h" #include "EngineController.h" #include "MediaDeviceMonitor.h" #include "MemoryQueryMaker.h" #include "SvgHandler.h" #include "handler/AudioCdHandler.h" #include "support/AudioCdConnectionAssistant.h" #include "support/AudioCdDeviceInfo.h" #include #include #include #include #include #include #include #include #include #include using namespace Collections; AMAROK_EXPORT_COLLECTION( AudioCdCollectionFactory, audiocdcollection ) static const QString unknownCddbId( "unknown" ); AudioCdCollectionFactory::AudioCdCollectionFactory( QObject *parent, const QVariantList &args ) : MediaDeviceCollectionFactory( parent, args, new AudioCdConnectionAssistant() ) { m_info = KPluginInfo( "amarok_collection-audiocdcollection.desktop", "services" ); } AudioCdCollection::AudioCdCollection( MediaDeviceInfo* info ) : MediaDeviceCollection() , m_encodingFormat( OGG ) { DEBUG_BLOCK // so that `amarok --cdplay` works: connect( this, SIGNAL(collectionReady(Collections::Collection*)), SLOT(checkForStartPlayRequest()) ); debug() << "Getting Audio CD info"; AudioCdDeviceInfo *cdInfo = qobject_cast( info ); m_udi = cdInfo->udi(); m_device = cdInfo->device(); readAudioCdSettings(); m_handler = new Meta::AudioCdHandler( this ); } AudioCdCollection::~AudioCdCollection() { } -KUrl +QUrl AudioCdCollection::audiocdUrl( const QString &path ) const { - KUrl url("audiocd:/"); - url.addPath( path ); + QUrl url("audiocd:/"); + url = url.adjusted(QUrl::StripTrailingSlash); + url.setPath(url.path() + '/' + ( path )); if( !m_device.isEmpty() ) url.addQueryItem( "device", m_device ); return url; } void AudioCdCollection::readCd() { DEBUG_BLOCK //get the CDDB info file if possible. KIO::ListJob *listJob = KIO::listRecursive( audiocdUrl(), KIO::HideProgressInfo, false ); connect( listJob, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), this, SLOT(audioCdEntries(KIO::Job*,KIO::UDSEntryList)) ); connect( listJob, SIGNAL(result(KJob*)), SLOT(slotEntriesJobDone(KJob*)) ); } void AudioCdCollection::audioCdEntries( KIO::Job *job, const KIO::UDSEntryList &list ) { DEBUG_BLOCK Q_UNUSED( job ) for( KIO::UDSEntryList::ConstIterator it = list.begin(); it != list.end(); ++it ) { const KIO::UDSEntry &entry = *it; QString name = entry.stringValue( KIO::UDSEntry::UDS_NAME ); if( name.endsWith( QLatin1String(".txt") ) ) m_cddbTextFiles.insert( entry.numberValue( KIO::UDSEntry::UDS_SIZE ), audiocdUrl( name ) ); } } void AudioCdCollection::slotEntriesJobDone( KJob *job ) { DEBUG_BLOCK if( job->error() ) warning() << __PRETTY_FUNCTION__ << job->errorString() << job->errorText(); if( m_cddbTextFiles.isEmpty() ) { warning() << __PRETTY_FUNCTION__ << "haven't found .txt file under audiocd:/, but continuing"; noInfoAvailable(); return; } int biggestTextFile = m_cddbTextFiles.keys().last(); - KUrl url = m_cddbTextFiles.value( biggestTextFile ); + QUrl url = m_cddbTextFiles.value( biggestTextFile ); m_cddbTextFiles.clear(); // save memory KIO::StoredTransferJob *tjob = KIO::storedGet( url, KIO::NoReload, KIO::HideProgressInfo ); connect( tjob, SIGNAL(result(KJob*)), SLOT(infoFetchComplete(KJob*)) ); } void AudioCdCollection::infoFetchComplete( KJob *job ) { DEBUG_BLOCK if( job->error() ) { error() << job->error() << job->errorString() << job->errorText(); job->deleteLater(); noInfoAvailable(); return; } KIO::StoredTransferJob *tjob = static_cast( job ); QString cddbInfo = tjob->data(); KEncodingProber prober; KEncodingProber::ProberState result = prober.feed( tjob->data() ); if( result != KEncodingProber::NotMe ) cddbInfo = QTextCodec::codecForName( prober.encoding() )->toUnicode( tjob->data() ); debug() << "Encoding: " << prober.encoding(); debug() << "got cddb info: " << cddbInfo; if (cddbInfo.length() == 0) { job->deleteLater(); noInfoAvailable(); return; } int startIndex; int endIndex; QString artist; QString album; QString year; QString genre; startIndex = cddbInfo.indexOf( "DTITLE=", 0 ); if ( startIndex != -1 ) { startIndex += 7; endIndex = cddbInfo.indexOf( "\n", startIndex ); QString compoundTitle = cddbInfo.mid( startIndex, endIndex - startIndex ); debug() << "compoundTitle: " << compoundTitle; QStringList compoundTitleList = compoundTitle.split( " / " ); artist = compoundTitleList.at( 0 ); album = compoundTitleList.at( 1 ); } Meta::AudioCdArtistPtr artistPtr = Meta::AudioCdArtistPtr( new Meta::AudioCdArtist( artist ) ); memoryCollection()->addArtist( Meta::ArtistPtr::staticCast( artistPtr ) ); Meta::AudioCdComposerPtr composerPtr = Meta::AudioCdComposerPtr( new Meta::AudioCdComposer( QString() ) ); memoryCollection()->addComposer( Meta::ComposerPtr::staticCast( composerPtr ) ); Meta::AudioCdAlbumPtr albumPtr = Meta::AudioCdAlbumPtr( new Meta::AudioCdAlbum( album ) ); albumPtr->setAlbumArtist( artistPtr ); memoryCollection()->addAlbum( Meta::AlbumPtr::staticCast( albumPtr ) ); startIndex = cddbInfo.indexOf( "DYEAR=", 0 ); if ( startIndex != -1 ) { startIndex += 6; endIndex = cddbInfo.indexOf( "\n", startIndex ); year = cddbInfo.mid( startIndex, endIndex - startIndex ); } Meta::AudioCdYearPtr yearPtr = Meta::AudioCdYearPtr( new Meta::AudioCdYear( year ) ); memoryCollection()->addYear( Meta::YearPtr::staticCast( yearPtr ) ); startIndex = cddbInfo.indexOf( "DGENRE=", 0 ); if ( startIndex != -1 ) { startIndex += 7; endIndex = cddbInfo.indexOf( "\n", startIndex ); genre = cddbInfo.mid( startIndex, endIndex - startIndex ); } Meta::AudioCdGenrePtr genrePtr = Meta::AudioCdGenrePtr( new Meta::AudioCdGenre( genre ) ); memoryCollection()->addGenre( Meta::GenrePtr::staticCast( genrePtr ) ); m_discCddbId = unknownCddbId; startIndex = cddbInfo.indexOf( "DISCID=", 0 ); if ( startIndex != -1 ) { startIndex += 7; endIndex = cddbInfo.indexOf( "\n", startIndex ); m_discCddbId = cddbInfo.mid( startIndex, endIndex - startIndex ); } //MediaDeviceMonitor::instance()->setCurrentCdId( m_discCddbId ); //get the list of tracknames startIndex = cddbInfo.indexOf( "TTITLE0=", 0 ); if ( startIndex != -1 ) { endIndex = cddbInfo.indexOf( "\nEXTD=", startIndex ); QString tracksBlock = cddbInfo.mid( startIndex, endIndex - startIndex ); debug() << "Tracks block: " << tracksBlock; QStringList tracksBlockList = tracksBlock.split( '\n' ); int numberOfTracks = tracksBlockList.count(); for ( int i = 0; i < numberOfTracks; i++ ) { QString prefix = "TTITLE" + QString::number( i ) + '='; debug() << "prefix: " << prefix; QString trackName = tracksBlockList.at( i ); trackName = trackName.remove( prefix ); QString trackArtist; //check if a track artist is included in the track name: if ( trackName.contains( " / " ) ) { QStringList trackArtistList = trackName.split( " / " ); trackName = trackArtistList.at( 1 ); trackArtist = trackArtistList.at( 0 ); } debug() << "Track name: " << trackName; QString padding = (i + 1) < 10 ? "0" : QString(); QString baseFileName = m_fileNamePattern; debug() << "Track Base File Name (before): " << baseFileName; baseFileName.replace( "%{title}", trackName, Qt::CaseInsensitive ); baseFileName.replace( "%{number}", padding + QString::number( i + 1 ), Qt::CaseInsensitive ); baseFileName.replace( "%{albumtitle}", album, Qt::CaseInsensitive ); baseFileName.replace( "%{trackartist}", trackArtist, Qt::CaseInsensitive ); baseFileName.replace( "%{albumartist}", artist, Qt::CaseInsensitive ); baseFileName.replace( "%{year}", year, Qt::CaseInsensitive ); baseFileName.replace( "%{genre}", genre, Qt::CaseInsensitive ); //we hack the url so the engine controller knows what track on the CD to play.. - KUrl baseUrl = audiocdUrl( m_discCddbId + '/' + QString::number( i + 1 ) ); + QUrl baseUrl = audiocdUrl( m_discCddbId + '/' + QString::number( i + 1 ) ); debug() << "Track Base File Name (after): " << baseFileName; debug() << "Track url: " << baseUrl; Meta::AudioCdTrackPtr trackPtr = Meta::AudioCdTrackPtr( new Meta::AudioCdTrack( this, trackName, baseUrl ) ); trackPtr->setTrackNumber( i + 1 ); trackPtr->setFileNameBase( baseFileName ); trackPtr->setLength( trackLength( i + 1 ) ); memoryCollection()->addTrack( Meta::TrackPtr::staticCast( trackPtr ) ); artistPtr->addTrack( trackPtr ); if ( trackArtist.isEmpty() ) trackPtr->setArtist( artistPtr ); else { albumPtr->setCompilation( true ); Meta::AudioCdArtistPtr trackArtistPtr = Meta::AudioCdArtistPtr( new Meta::AudioCdArtist( trackArtist ) ); trackArtistPtr->addTrack( trackPtr ); trackPtr->setArtist( trackArtistPtr ); } composerPtr->addTrack( trackPtr ); trackPtr->setComposer( composerPtr ); albumPtr->addTrack( trackPtr ); trackPtr->setAlbum( albumPtr ); genrePtr->addTrack( trackPtr ); trackPtr->setGenre( genrePtr ); yearPtr->addTrack( trackPtr ); trackPtr->setYear( yearPtr ); } } //lets see if we can find a cover for the album: if( AmarokConfig::autoGetCoverArt() ) The::coverFetcher()->queueAlbum( Meta::AlbumPtr::staticCast( albumPtr ) ); updateProxyTracks(); emit collectionReady( this ); } void AudioCdCollection::checkForStartPlayRequest() { //be nice and check if MainWindow is just aching for an audio cd to start playing if( The::mainWindow()->isWaitingForCd() ) { debug() << "Tell MainWindow to start playing us immediately."; The::mainWindow()->playAudioCd(); } } qint64 AudioCdCollection::trackLength(int i) const { - KUrl kioUrl = audiocdUrl( QString("Track%1.wav").arg(i, 2, 10, QChar('0') ) ); + QUrl kioUrl = audiocdUrl( QString("Track%1.wav").arg(i, 2, 10, QChar('0') ) ); KIO::UDSEntry uds; if ( KIO::NetAccess::stat(kioUrl, uds, NULL) ) { qint64 samples = (uds.numberValue(KIO::UDSEntry::UDS_SIZE, 44) - 44) / 4; return (samples - 44) * 10 / 441; } return 0; } QString AudioCdCollection::collectionId() const { return QLatin1String( "AudioCd" ); } QString AudioCdCollection::prettyName() const { return i18n( "Audio CD" ); } KIcon AudioCdCollection::icon() const { return KIcon( "media-optical-audio" ); } void AudioCdCollection::cdRemoved() { emit remove(); } QString AudioCdCollection::encodingFormat() const { switch( m_encodingFormat ) { case WAV: return "wav"; case FLAC: return "flac"; case OGG: return "ogg"; case MP3: return "mp3"; } return QString(); } QString AudioCdCollection::copyableFilePath( const QString &fileName ) const { switch( m_encodingFormat ) { case WAV: return audiocdUrl( fileName ).url(); case FLAC: return audiocdUrl( "FLAC/" + fileName ).url(); case OGG: return audiocdUrl( "Ogg Vorbis/" + fileName ).url(); case MP3: return audiocdUrl( "MP3/" + fileName ).url(); } return QString(); } void AudioCdCollection::setEncodingFormat( int format ) const { m_encodingFormat = format; } CollectionLocation * AudioCdCollection::location() { return new AudioCdCollectionLocation( this ); } void AudioCdCollection::eject() { DEBUG_BLOCK //we need to do a quick check if we are currently playing from this cd, if so, stop playback and then eject Meta::TrackPtr track = The::engineController()->currentTrack(); if ( track ) { if( track->playableUrl().url().startsWith( "audiocd:/" ) ) The::engineController()->stop(); } Solid::Device device = Solid::Device( m_udi ); Solid::OpticalDrive *drive = device.parent().as(); if( drive ) drive->eject(); else debug() << "disc has no drive"; } void AudioCdCollection::noInfoAvailable() { DEBUG_BLOCK m_discCddbId = unknownCddbId; //MediaDeviceMonitor::instance()->setCurrentCdId( m_discCddbId ); QString artist = i18n( "Unknown" ); QString album = i18n( "Unknown" ); QString year = i18n( "Unknown" ); QString genre = i18n( "Unknown" ); Meta::AudioCdArtistPtr artistPtr = Meta::AudioCdArtistPtr( new Meta::AudioCdArtist( artist ) ); memoryCollection()->addArtist( Meta::ArtistPtr::staticCast( artistPtr ) ); Meta::AudioCdComposerPtr composerPtr = Meta::AudioCdComposerPtr( new Meta::AudioCdComposer( QString() ) ); memoryCollection()->addComposer( Meta::ComposerPtr::staticCast( composerPtr ) ); Meta::AudioCdAlbumPtr albumPtr = Meta::AudioCdAlbumPtr( new Meta::AudioCdAlbum( album ) ); albumPtr->setAlbumArtist( artistPtr ); memoryCollection()->addAlbum( Meta::AlbumPtr::staticCast( albumPtr ) ); Meta::AudioCdYearPtr yearPtr = Meta::AudioCdYearPtr( new Meta::AudioCdYear( year ) ); memoryCollection()->addYear( Meta::YearPtr::staticCast( yearPtr ) ); Meta::AudioCdGenrePtr genrePtr = Meta::AudioCdGenrePtr( new Meta::AudioCdGenre( genre ) ); memoryCollection()->addGenre( Meta::GenrePtr::staticCast( genrePtr ) ); int i = 1; QString prefix( "0" ); QString trackName = "Track " + prefix + QString::number( i ); while( KIO::NetAccess::exists( QString( "audiocd:/" + trackName + ".wav" ), KIO::NetAccess::SourceSide, 0 ) ) { debug() << "got track: " << "audiocd:/" + trackName + ".wav"; QString baseUrl = "audiocd:/" + m_discCddbId + '/' + QString::number( i ); Meta::AudioCdTrackPtr trackPtr = Meta::AudioCdTrackPtr( new Meta::AudioCdTrack( this, trackName, baseUrl ) ); trackPtr->setTrackNumber( i ); trackPtr->setFileNameBase( trackName ); trackPtr->setLength( trackLength( i ) ); memoryCollection()->addTrack( Meta::TrackPtr::staticCast( trackPtr ) ); artistPtr->addTrack( trackPtr ); trackPtr->setArtist( artistPtr ); composerPtr->addTrack( trackPtr ); trackPtr->setComposer( composerPtr ); albumPtr->addTrack( trackPtr ); trackPtr->setAlbum( albumPtr ); genrePtr->addTrack( trackPtr ); trackPtr->setGenre( genrePtr ); yearPtr->addTrack( trackPtr ); trackPtr->setYear( yearPtr ); i++; prefix = i < 10 ? "0" : ""; trackName = "Track " + prefix + QString::number( i ); } updateProxyTracks(); emit collectionReady( this ); } void AudioCdCollection::readAudioCdSettings() { KSharedConfigPtr conf = KSharedConfig::openConfig( "kcmaudiocdrc" ); KConfigGroup filenameConf = conf->group( "FileName" ); m_fileNamePattern = filenameConf.readEntry( "file_name_template", "%{trackartist} - %{number} - %{title}" ); m_albumNamePattern = filenameConf.readEntry( "album_name_template", "%{albumartist} - %{albumtitle}" ); } bool -AudioCdCollection::possiblyContainsTrack( const KUrl &url ) const +AudioCdCollection::possiblyContainsTrack( const QUrl &url ) const { - return url.protocol() == "audiocd"; + return url.scheme() == "audiocd"; } Meta::TrackPtr -AudioCdCollection::trackForUrl( const KUrl &url ) +AudioCdCollection::trackForUrl( const QUrl &url ) { QReadLocker locker( memoryCollection()->mapLock() ); if( memoryCollection()->trackMap().contains( url.url() ) ) return memoryCollection()->trackMap().value( url.url() ); QRegExp trackUrlScheme( "^audiocd:/([a-zA-Z0-9]*)/([0-9]{1,})" ); if( trackUrlScheme.indexIn( url.url() ) != 0 ) { warning() << __PRETTY_FUNCTION__ << url.url() << "doesn't have correct scheme" << trackUrlScheme; return Meta::TrackPtr(); } const QString trackCddbId = trackUrlScheme.capturedTexts().value( 1 ); const int trackNumber = trackUrlScheme.capturedTexts().value( 2 ).toInt(); if( !trackCddbId.isEmpty() && trackCddbId != unknownCddbId && !m_discCddbId.isEmpty() && m_discCddbId != unknownCddbId && trackCddbId != m_discCddbId ) { warning() << __PRETTY_FUNCTION__ << "track with cddbId" << trackCddbId << "doesn't match our cddbId" << m_discCddbId; return Meta::TrackPtr(); } foreach( const Meta::TrackPtr &track, memoryCollection()->trackMap() ) { if( track->trackNumber() == trackNumber ) return track; } warning() << __PRETTY_FUNCTION__ << "track with number" << trackNumber << "not found"; return Meta::TrackPtr(); } void AudioCdCollection::updateProxyTracks() { - foreach( const KUrl &url, m_proxyMap.keys() ) + foreach( const QUrl &url, m_proxyMap.keys() ) { QString urlString = url.url().remove( "audiocd:/" ); const QStringList &parts = urlString.split( '/' ); if( parts.count() != 2 ) continue; const QString &discId = parts.at( 0 ); if( discId != m_discCddbId ) continue; const int trackNumber = parts.at( 1 ).toInt(); foreach( const Meta::TrackPtr &track, memoryCollection()->trackMap().values() ) { if( track->trackNumber() == trackNumber ) { m_proxyMap.value( url )->updateTrack( track ); } } } m_proxyMap.clear(); } void AudioCdCollection::startFullScan() { DEBUG_BLOCK readCd(); } diff --git a/src/core-impl/collections/audiocd/AudioCdCollection.h b/src/core-impl/collections/audiocd/AudioCdCollection.h index dc2cad755c..85bd86fc1a 100644 --- a/src/core-impl/collections/audiocd/AudioCdCollection.h +++ b/src/core-impl/collections/audiocd/AudioCdCollection.h @@ -1,134 +1,134 @@ /**************************************************************************************** * Copyright (c) 2009 Nikolaj Hald Nielsen * * * * 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, see . * ****************************************************************************************/ #ifndef AUDIOCDCOLLECTION_H #define AUDIOCDCOLLECTION_H #include "core/collections/Collection.h" #include "MediaDeviceCollection.h" #include "MemoryCollection.h" #include "core-impl/meta/proxy/MetaProxy.h" -#include +#include #include #include class MediaDeviceInfo; namespace Collections { class AudioCdCollection; class AudioCdCollectionFactory : public MediaDeviceCollectionFactory { Q_OBJECT public: AudioCdCollectionFactory( QObject *parent, const QVariantList &args ); virtual ~AudioCdCollectionFactory() {}; /* virtual void init(); private slots: void audioCdAdded( const QString &uid ); void deviceRemoved( const QString &uid ); private: QString m_currentUid; AudioCdCollection * m_collection;*/ }; /** * This is a Memorycollection sublclass that uses the KIO audiocd:/ slave to * populate itself whenever it detects a CD. * * @author Nikolaj Hald Nielsen */ class AudioCdCollection : public MediaDeviceCollection { Q_OBJECT public: enum { WAV, FLAC, OGG, MP3 } EncodingFormat; AudioCdCollection( MediaDeviceInfo* info ); ~AudioCdCollection(); QString encodingFormat() const; QString copyableFilePath( const QString &fileName ) const; void setEncodingFormat( int format ) const; virtual QString collectionId() const; virtual QString prettyName() const; virtual KIcon icon() const; virtual CollectionLocation* location(); - virtual bool possiblyContainsTrack( const KUrl &url ) const; - virtual Meta::TrackPtr trackForUrl( const KUrl &url ); + virtual bool possiblyContainsTrack( const QUrl &url ) const; + virtual Meta::TrackPtr trackForUrl( const QUrl &url ); void cdRemoved(); virtual void startFullScan(); //Override this one as I really don't want to move parsing to the handler atm. virtual void startFullScanDevice() { startFullScan(); } public slots: virtual void eject(); private slots: void audioCdEntries( KIO::Job *job, const KIO::UDSEntryList &list ); void slotEntriesJobDone( KJob *job ); void infoFetchComplete( KJob *job ); void checkForStartPlayRequest(); private: void readAudioCdSettings(); // Helper function to build the audiocd url. - KUrl audiocdUrl( const QString &path = "" ) const; + QUrl audiocdUrl( const QString &path = "" ) const; qint64 trackLength( int i ) const; /** * Clear collection and read the CD currently in the drive, adding Artist, Album, * Genre, Year and whatnot as detected by audiocd using CDDB. */ void readCd(); void noInfoAvailable(); void updateProxyTracks(); - QMap m_cddbTextFiles; + QMap m_cddbTextFiles; QString m_cdName; QString m_discCddbId; QString m_udi; QString m_device; mutable int m_encodingFormat; QString m_fileNamePattern; QString m_albumNamePattern; - QMap m_proxyMap; + QMap m_proxyMap; }; } //namespace Collections #endif diff --git a/src/core-impl/collections/audiocd/AudioCdCollectionLocation.cpp b/src/core-impl/collections/audiocd/AudioCdCollectionLocation.cpp index 97d4270b58..59d14ca174 100644 --- a/src/core-impl/collections/audiocd/AudioCdCollectionLocation.cpp +++ b/src/core-impl/collections/audiocd/AudioCdCollectionLocation.cpp @@ -1,89 +1,89 @@ /**************************************************************************************** * Copyright (c) 2009 Nikolaj Hald Nielsen * * * * 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, see . * ****************************************************************************************/ #include "AudioCdCollectionLocation.h" #include "AudioCdMeta.h" #include "core/support/Debug.h" #include "FormatSelectionDialog.h" using namespace Collections; AudioCdCollectionLocation::AudioCdCollectionLocation( AudioCdCollection *parentCollection ) : CollectionLocation( parentCollection ) , m_collection( parentCollection ) { } AudioCdCollectionLocation::~AudioCdCollectionLocation() { } void AudioCdCollectionLocation::getKIOCopyableUrls( const Meta::TrackList & tracks ) { DEBUG_BLOCK - QMap resultMap; + QMap resultMap; foreach( Meta::TrackPtr trackPtr, tracks ) { Meta::AudioCdTrackPtr cdTrack = Meta::AudioCdTrackPtr::staticCast( trackPtr ); const QString path = m_collection->copyableFilePath( cdTrack->fileNameBase() + '.' + m_collection->encodingFormat() ); - resultMap.insert( trackPtr, KUrl( path ) ); + resultMap.insert( trackPtr, QUrl( path ) ); } slotGetKIOCopyableUrlsDone( resultMap ); } void AudioCdCollectionLocation::showSourceDialog( const Meta::TrackList &tracks, bool removeSources ) { DEBUG_BLOCK Q_UNUSED( tracks ) Q_UNUSED( removeSources ) FormatSelectionDialog * dlg = new FormatSelectionDialog(); connect( dlg, SIGNAL(formatSelected(int)), this, SLOT(onFormatSelected(int)) ); connect( dlg, SIGNAL(rejected()), this, SLOT(onCancel()) ); dlg->show(); } void AudioCdCollectionLocation::formatSelected( int format ) { Q_UNUSED( format ) } void AudioCdCollectionLocation::formatSelectionCancelled() { } void AudioCdCollectionLocation::onFormatSelected( int format ) { DEBUG_BLOCK m_collection->setEncodingFormat( format ); slotShowSourceDialogDone(); } void AudioCdCollectionLocation::onCancel() { DEBUG_BLOCK abort(); } diff --git a/src/core-impl/collections/audiocd/AudioCdMeta.cpp b/src/core-impl/collections/audiocd/AudioCdMeta.cpp index 6007e871bb..9ed98b3ff7 100644 --- a/src/core-impl/collections/audiocd/AudioCdMeta.cpp +++ b/src/core-impl/collections/audiocd/AudioCdMeta.cpp @@ -1,484 +1,484 @@ /**************************************************************************************** * Copyright (c) 2007 Maximilian Kossick * * Copyright (c) 2009 Nikolaj Hald Nielsen * * * * 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, see . * ****************************************************************************************/ #include "AudioCdMeta.h" #include "AudioCdCollection.h" #include "core/support/Debug.h" #include "covermanager/CoverCache.h" using namespace Meta; -AudioCdTrack::AudioCdTrack( Collections::AudioCdCollection *collection, const QString &name, const KUrl &url ) +AudioCdTrack::AudioCdTrack( Collections::AudioCdCollection *collection, const QString &name, const QUrl &url ) : Meta::Track() , m_collection( collection ) , m_artist( 0 ) , m_album( 0 ) , m_genre( 0 ) , m_composer( 0 ) , m_year( 0 ) , m_name( name) , m_length( 0 ) , m_trackNumber( 0 ) , m_playableUrl( url ) { } AudioCdTrack::~AudioCdTrack() { //nothing to do } QString AudioCdTrack::name() const { return m_name; } -KUrl +QUrl AudioCdTrack::playableUrl() const { return m_playableUrl; } QString AudioCdTrack::uidUrl() const { return m_playableUrl.url(); } QString AudioCdTrack::prettyUrl() const { - return m_playableUrl.prettyUrl(); + return m_playableUrl.toDisplayString(); } QString AudioCdTrack::notPlayableReason() const { //TODO: check availablity of correct CD somehow return QString(); } AlbumPtr AudioCdTrack::album() const { return AlbumPtr::staticCast( m_album ); } ArtistPtr AudioCdTrack::artist() const { return ArtistPtr::staticCast( m_artist ); } GenrePtr AudioCdTrack::genre() const { return GenrePtr::staticCast( m_genre ); } ComposerPtr AudioCdTrack::composer() const { return ComposerPtr::staticCast( m_composer ); } YearPtr AudioCdTrack::year() const { return YearPtr::staticCast( m_year ); } qreal AudioCdTrack::bpm() const { return -1.0; } QString AudioCdTrack::comment() const { return QString(); } void AudioCdTrack::setComment( const QString &newComment ) { Q_UNUSED( newComment ) } qint64 AudioCdTrack::length() const { return m_length; } int AudioCdTrack::filesize() const { return 0; } int AudioCdTrack::sampleRate() const { return 0; } int AudioCdTrack::bitrate() const { return 0; } int AudioCdTrack::trackNumber() const { return m_trackNumber; } void AudioCdTrack::setTrackNumber( int newTrackNumber ) { m_trackNumber = newTrackNumber; } int AudioCdTrack::discNumber() const { return 0; } void AudioCdTrack::setDiscNumber( int newDiscNumber ) { Q_UNUSED( newDiscNumber ) } QString AudioCdTrack::type() const { return m_collection->encodingFormat(); } bool AudioCdTrack::inCollection() const { return true; } Collections::Collection* AudioCdTrack::collection() const { return m_collection; } void AudioCdTrack::setAlbum( AudioCdAlbumPtr album ) { m_album = album; } void AudioCdTrack::setArtist( AudioCdArtistPtr artist ) { m_artist = artist; } void AudioCdTrack::setGenre( AudioCdGenrePtr genre ) { m_genre = genre; } void AudioCdTrack::setComposer( AudioCdComposerPtr composer ) { m_composer = composer; } void AudioCdTrack::setYear( AudioCdYearPtr year ) { m_year = year; } void AudioCdTrack::setTitle( const QString &title ) { m_name = title; } void AudioCdTrack::setLength( qint64 length ) { m_length = length; } void Meta::AudioCdTrack::setFileNameBase( const QString & fileNameBase ) { m_fileNameBase = fileNameBase; } QString Meta::AudioCdTrack::fileNameBase() { return m_fileNameBase; } //AudioCdArtist AudioCdArtist::AudioCdArtist( const QString &name ) : Meta::Artist() , m_name( name ) , m_tracks() { //nothing to do } AudioCdArtist::~AudioCdArtist() { //nothing to do } QString AudioCdArtist::name() const { return m_name; } TrackList AudioCdArtist::tracks() { return m_tracks; } AlbumList AudioCdArtist::albums() { //TODO return AlbumList(); } void AudioCdArtist::addTrack( AudioCdTrackPtr track ) { m_tracks.append( TrackPtr::staticCast( track ) ); } AudioCdAlbum::AudioCdAlbum( const QString &name ) : Meta::Album() , m_name( name ) , m_tracks() , m_isCompilation( false ) , m_albumArtist( 0 ) { //nothing to do } AudioCdAlbum::~AudioCdAlbum() { CoverCache::invalidateAlbum( this ); } QString AudioCdAlbum::name() const { return m_name; } bool AudioCdAlbum::isCompilation() const { return m_isCompilation; } bool AudioCdAlbum::canUpdateCompilation() const { return true; } void AudioCdAlbum::setCompilation( bool compilation ) { m_isCompilation = compilation; } bool AudioCdAlbum::hasAlbumArtist() const { return !m_albumArtist.isNull(); } ArtistPtr AudioCdAlbum::albumArtist() const { return ArtistPtr::staticCast( m_albumArtist ); } TrackList AudioCdAlbum::tracks() { return m_tracks; } QImage AudioCdAlbum::image( int size ) const { if ( m_cover.isNull() ) return Meta::Album::image( size ); else return m_cover.scaled( size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation ); } bool AudioCdAlbum::hasImage( int size ) const { if ( m_cover.isNull() ) return Meta::Album::hasImage( size ); else return true; } bool AudioCdAlbum::canUpdateImage() const { return false; } void AudioCdAlbum::setImage( const QImage &image ) { m_cover = image; CoverCache::invalidateAlbum( this ); } void AudioCdAlbum::addTrack( AudioCdTrackPtr track ) { m_tracks.append( TrackPtr::staticCast( track ) ); } void AudioCdAlbum::setAlbumArtist( AudioCdArtistPtr artist ) { m_albumArtist = artist; } //AudioCdGenre AudioCdGenre::AudioCdGenre( const QString &name ) : Meta::Genre() , m_name( name ) , m_tracks() { //nothing to do } AudioCdGenre::~AudioCdGenre() { //nothing to do } QString AudioCdGenre::name() const { return m_name; } TrackList AudioCdGenre::tracks() { return m_tracks; } void AudioCdGenre::addTrack( AudioCdTrackPtr track ) { m_tracks.append( TrackPtr::staticCast( track ) ); } //AudioCdComposer AudioCdComposer::AudioCdComposer( const QString &name ) : Meta::Composer() , m_name( name ) , m_tracks() { //nothing to do } AudioCdComposer::~AudioCdComposer() { //nothing to do } QString AudioCdComposer::name() const { return m_name; } TrackList AudioCdComposer::tracks() { return m_tracks; } void AudioCdComposer::addTrack( AudioCdTrackPtr track ) { m_tracks.append( TrackPtr::staticCast( track ) ); } //AudioCdYear AudioCdYear::AudioCdYear( const QString &name ) : Meta::Year() , m_name( name ) , m_tracks() { //nothing to do } AudioCdYear::~AudioCdYear() { //nothing to do } QString AudioCdYear::name() const { return m_name; } TrackList AudioCdYear::tracks() { return m_tracks; } void AudioCdYear::addTrack( AudioCdTrackPtr track ) { m_tracks.append( TrackPtr::staticCast( track ) ); } diff --git a/src/core-impl/collections/audiocd/AudioCdMeta.h b/src/core-impl/collections/audiocd/AudioCdMeta.h index af1f766a74..6e09f722b6 100644 --- a/src/core-impl/collections/audiocd/AudioCdMeta.h +++ b/src/core-impl/collections/audiocd/AudioCdMeta.h @@ -1,225 +1,225 @@ /**************************************************************************************** * Copyright (c) 2007 Maximilian Kossick * * Copyright (c) 2009 Nikolaj Hald Nielsen * * * * 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, see . * ****************************************************************************************/ #ifndef AUDIOCDMETA_H #define AUDIOCDMETA_H #include "core/meta/Meta.h" namespace Collections { class AudioCdCollection; } namespace Meta { class AudioCdTrack; class AudioCdAlbum; class AudioCdArtist; class AudioCdGenre; class AudioCdComposer; class AudioCdYear; typedef KSharedPtr AudioCdTrackPtr; typedef KSharedPtr AudioCdArtistPtr; typedef KSharedPtr AudioCdAlbumPtr; typedef KSharedPtr AudioCdGenrePtr; typedef KSharedPtr AudioCdComposerPtr; typedef KSharedPtr AudioCdYearPtr; class AudioCdTrack : public Meta::Track { public: - AudioCdTrack( Collections::AudioCdCollection *collection, const QString &name, const KUrl &url ); + AudioCdTrack( Collections::AudioCdCollection *collection, const QString &name, const QUrl &url ); virtual ~AudioCdTrack(); virtual QString name() const; - virtual KUrl playableUrl() const; + virtual QUrl playableUrl() const; virtual QString uidUrl() const; virtual QString prettyUrl() const; virtual QString notPlayableReason() const; virtual AlbumPtr album() const; virtual ArtistPtr artist() const; virtual GenrePtr genre() const; virtual ComposerPtr composer() const; virtual YearPtr year() const; virtual void setTitle( const QString &newTitle ); virtual qreal bpm() const; virtual QString comment() const; virtual void setComment ( const QString &newComment ); virtual qint64 length() const; virtual int filesize() const; virtual int sampleRate() const; virtual int bitrate() const; virtual int trackNumber() const; virtual void setTrackNumber ( int newTrackNumber ); virtual int discNumber() const; virtual void setDiscNumber ( int newDiscNumber ); virtual QString type() const; virtual bool inCollection() const; virtual Collections::Collection* collection() const; //AudioCdTrack specific methods void setAlbum( AudioCdAlbumPtr album ); void setArtist( AudioCdArtistPtr artist ); void setComposer( AudioCdComposerPtr composer ); void setGenre( AudioCdGenrePtr genre ); void setYear( AudioCdYearPtr year ); void setLength( qint64 length ); void setFileNameBase( const QString &fileNameBase ); QString fileNameBase(); private: Collections::AudioCdCollection *m_collection; AudioCdArtistPtr m_artist; AudioCdAlbumPtr m_album; AudioCdGenrePtr m_genre; AudioCdComposerPtr m_composer; AudioCdYearPtr m_year; QString m_name; qint64 m_length; int m_trackNumber; - KUrl m_playableUrl; + QUrl m_playableUrl; QString m_fileNameBase; }; class AudioCdArtist : public Meta::Artist { public: AudioCdArtist( const QString &name ); virtual ~AudioCdArtist(); virtual QString name() const; virtual TrackList tracks(); virtual AlbumList albums(); //AudioCdArtist specific methods void addTrack( AudioCdTrackPtr track ); private: QString m_name; TrackList m_tracks; }; class AudioCdAlbum : public Meta::Album { public: AudioCdAlbum( const QString &name ); virtual ~AudioCdAlbum(); virtual QString name() const; virtual bool isCompilation() const; virtual bool canUpdateCompilation() const; virtual void setCompilation( bool compilation ); virtual bool hasAlbumArtist() const; virtual ArtistPtr albumArtist() const; virtual TrackList tracks(); virtual QImage image( int size = 0 ) const; virtual bool hasImage( int size = 0 ) const; virtual bool canUpdateImage() const; virtual void setImage( const QImage &image ); //AudioCdAlbum specific methods void addTrack( AudioCdTrackPtr track ); void setAlbumArtist( AudioCdArtistPtr artist ); private: QString m_name; TrackList m_tracks; bool m_isCompilation; AudioCdArtistPtr m_albumArtist; QImage m_cover; }; class AudioCdGenre : public Meta::Genre { public: AudioCdGenre( const QString &name ); virtual ~AudioCdGenre(); virtual QString name() const; virtual TrackList tracks(); //AudioCdGenre specific methods void addTrack( AudioCdTrackPtr track ); private: QString m_name; TrackList m_tracks; }; class AudioCdComposer : public Meta::Composer { public: AudioCdComposer( const QString &name ); virtual ~AudioCdComposer(); virtual QString name() const; virtual TrackList tracks(); //AudioCdComposer specific methods void addTrack( AudioCdTrackPtr track ); private: QString m_name; TrackList m_tracks; }; class AudioCdYear : public Meta::Year { public: AudioCdYear( const QString &name ); virtual ~AudioCdYear(); virtual QString name() const; virtual TrackList tracks(); //AudioCdYear specific methods void addTrack( AudioCdTrackPtr track ); private: QString m_name; TrackList m_tracks; }; } #endif diff --git a/src/core-impl/collections/daap/DaapMeta.cpp b/src/core-impl/collections/daap/DaapMeta.cpp index 12c39dc889..02a35ddddc 100644 --- a/src/core-impl/collections/daap/DaapMeta.cpp +++ b/src/core-impl/collections/daap/DaapMeta.cpp @@ -1,466 +1,466 @@ /**************************************************************************************** * Copyright (c) 2007 Maximilian Kossick * * * * 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, see . * ****************************************************************************************/ #include "DaapMeta.h" #include "DaapCollection.h" using namespace Meta; DaapTrack::DaapTrack( Collections::DaapCollection *collection, const QString &host, quint16 port, const QString &dbId, const QString &itemId, const QString &format) : Meta::Track() , m_collection( collection ) , m_artist( 0 ) , m_album( 0 ) , m_genre( 0 ) , m_composer( 0 ) , m_year( 0 ) , m_name() , m_type( format ) , m_length( 0 ) , m_trackNumber( 0 ) , m_displayUrl() , m_playableUrl() { QString url = QString( "daap://%1:%2/databases/%3/items/%4.%5" ) .arg( host, QString::number( port ), dbId, itemId, format ); m_displayUrl = url; m_playableUrl = url; } DaapTrack::~DaapTrack() { //nothing to do } QString DaapTrack::name() const { return m_name; } -KUrl +QUrl DaapTrack::playableUrl() const { - KUrl url( m_playableUrl ); - url.setProtocol( "http" ); + QUrl url( m_playableUrl ); + url.setScheme( "http" ); return url; } QString DaapTrack::uidUrl() const { return m_playableUrl; } QString DaapTrack::prettyUrl() const { return m_displayUrl; } QString DaapTrack::notPlayableReason() const { return networkNotPlayableReason(); } AlbumPtr DaapTrack::album() const { return AlbumPtr::staticCast( m_album ); } ArtistPtr DaapTrack::artist() const { return ArtistPtr::staticCast( m_artist ); } GenrePtr DaapTrack::genre() const { return GenrePtr::staticCast( m_genre ); } ComposerPtr DaapTrack::composer() const { return ComposerPtr::staticCast( m_composer ); } YearPtr DaapTrack::year() const { return YearPtr::staticCast( m_year ); } void DaapTrack::setAlbum( const QString &newAlbum ) { Q_UNUSED( newAlbum ) } void DaapTrack::setArtist( const QString &newArtist ) { Q_UNUSED( newArtist ) } void DaapTrack::setComposer( const QString &newComposer ) { Q_UNUSED( newComposer ) } void DaapTrack::setGenre( const QString &newGenre ) { Q_UNUSED( newGenre ) } void DaapTrack::setYear( int newYear ) { Q_UNUSED( newYear ) } /* TODO: This isn't good enough, but for now as daapreader/Reader.cpp indicates we can query for the BPM from daap server, but desire is to get BPM of files working first! */ qreal DaapTrack::bpm() const { return -1.0; } QString DaapTrack::comment() const { return QString(); } void DaapTrack::setComment( const QString &newComment ) { Q_UNUSED( newComment ) } qint64 DaapTrack::length() const { return m_length; } int DaapTrack::filesize() const { return 0; } int DaapTrack::sampleRate() const { return 0; } int DaapTrack::bitrate() const { return 0; } int DaapTrack::trackNumber() const { return m_trackNumber; } void DaapTrack::setTrackNumber( int newTrackNumber ) { m_trackNumber = newTrackNumber; } int DaapTrack::discNumber() const { return 0; } void DaapTrack::setDiscNumber( int newDiscNumber ) { Q_UNUSED( newDiscNumber ) } QString DaapTrack::type() const { return m_type; } bool DaapTrack::inCollection() const { return true; } Collections::Collection* DaapTrack::collection() const { return m_collection; } void DaapTrack::setAlbum( DaapAlbumPtr album ) { m_album = album; } void DaapTrack::setArtist( DaapArtistPtr artist ) { m_artist = artist; } void DaapTrack::setGenre( DaapGenrePtr genre ) { m_genre = genre; } void DaapTrack::setComposer( DaapComposerPtr composer ) { m_composer = composer; } void DaapTrack::setYear( DaapYearPtr year ) { m_year = year; } void DaapTrack::setTitle( const QString &title ) { m_name = title; } void DaapTrack::setLength( qint64 length ) { m_length = length; } //DaapArtist DaapArtist::DaapArtist( const QString &name ) : Meta::Artist() , m_name( name ) , m_tracks() { //nothing to do } DaapArtist::~DaapArtist() { //nothing to do } QString DaapArtist::name() const { return m_name; } TrackList DaapArtist::tracks() { return m_tracks; } AlbumList DaapArtist::albums() { //TODO return AlbumList(); } void DaapArtist::addTrack( DaapTrackPtr track ) { m_tracks.append( TrackPtr::staticCast( track ) ); } DaapAlbum::DaapAlbum( const QString &name ) : Meta::Album() , m_name( name ) , m_tracks() , m_isCompilation( false ) , m_albumArtist( 0 ) { //nothing to do } DaapAlbum::~DaapAlbum() { //nothing to do } QString DaapAlbum::name() const { return m_name; } bool DaapAlbum::isCompilation() const { return m_isCompilation; } bool DaapAlbum::hasAlbumArtist() const { return !m_albumArtist.isNull(); } ArtistPtr DaapAlbum::albumArtist() const { return ArtistPtr::staticCast( m_albumArtist ); } TrackList DaapAlbum::tracks() { return m_tracks; } void DaapAlbum::addTrack( DaapTrackPtr track ) { m_tracks.append( TrackPtr::staticCast( track ) ); } void DaapAlbum::setAlbumArtist( DaapArtistPtr artist ) { m_albumArtist = artist; } //DaapGenre DaapGenre::DaapGenre( const QString &name ) : Meta::Genre() , m_name( name ) , m_tracks() { //nothing to do } DaapGenre::~DaapGenre() { //nothing to do } QString DaapGenre::name() const { return m_name; } TrackList DaapGenre::tracks() { return m_tracks; } void DaapGenre::addTrack( DaapTrackPtr track ) { m_tracks.append( TrackPtr::staticCast( track ) ); } //DaapComposer DaapComposer::DaapComposer( const QString &name ) : Meta::Composer() , m_name( name ) , m_tracks() { //nothing to do } DaapComposer::~DaapComposer() { //nothing to do } QString DaapComposer::name() const { return m_name; } TrackList DaapComposer::tracks() { return m_tracks; } void DaapComposer::addTrack( DaapTrackPtr track ) { m_tracks.append( TrackPtr::staticCast( track ) ); } //DaapYear DaapYear::DaapYear( const QString &name ) : Meta::Year() , m_name( name ) , m_tracks() { //nothing to do } DaapYear::~DaapYear() { //nothing to do } QString DaapYear::name() const { return m_name; } TrackList DaapYear::tracks() { return m_tracks; } void DaapYear::addTrack( DaapTrackPtr track ) { m_tracks.append( TrackPtr::staticCast( track ) ); } diff --git a/src/core-impl/collections/daap/DaapMeta.h b/src/core-impl/collections/daap/DaapMeta.h index 7fcbfc9c1f..e0053d3738 100644 --- a/src/core-impl/collections/daap/DaapMeta.h +++ b/src/core-impl/collections/daap/DaapMeta.h @@ -1,219 +1,219 @@ /**************************************************************************************** * Copyright (c) 2007 Maximilian Kossick * * * * 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, see . * ****************************************************************************************/ #ifndef DAAPMETA_H #define DAAPMETA_H #include "core/meta/Meta.h" namespace Collections { class DaapCollection; } namespace Meta { class DaapTrack; class DaapAlbum; class DaapArtist; class DaapGenre; class DaapComposer; class DaapYear; typedef KSharedPtr DaapTrackPtr; typedef KSharedPtr DaapArtistPtr; typedef KSharedPtr DaapAlbumPtr; typedef KSharedPtr DaapGenrePtr; typedef KSharedPtr DaapComposerPtr; typedef KSharedPtr DaapYearPtr; class DaapTrack : public Meta::Track { public: DaapTrack( Collections::DaapCollection *collection, const QString &host, quint16 port, const QString &dbId, const QString &itemId, const QString &format); virtual ~DaapTrack(); virtual QString name() const; - virtual KUrl playableUrl() const; + virtual QUrl playableUrl() const; virtual QString uidUrl() const; virtual QString prettyUrl() const; virtual QString notPlayableReason() const; virtual AlbumPtr album() const; virtual ArtistPtr artist() const; virtual GenrePtr genre() const; virtual ComposerPtr composer() const; virtual YearPtr year() const; virtual void setAlbum ( const QString &newAlbum ); virtual void setArtist ( const QString &newArtist ); virtual void setGenre ( const QString &newGenre ); virtual void setComposer ( const QString &newComposer ); virtual void setYear ( int newYear ); virtual void setTitle( const QString &newTitle ); virtual qreal bpm() const; virtual QString comment() const; virtual void setComment ( const QString &newComment ); virtual qint64 length() const; virtual int filesize() const; virtual int sampleRate() const; virtual int bitrate() const; virtual int trackNumber() const; virtual void setTrackNumber ( int newTrackNumber ); virtual int discNumber() const; virtual void setDiscNumber ( int newDiscNumber ); virtual QString type() const; virtual bool inCollection() const; virtual Collections::Collection* collection() const; //DaapTrack specific methods void setAlbum( DaapAlbumPtr album ); void setArtist( DaapArtistPtr artist ); void setComposer( DaapComposerPtr composer ); void setGenre( DaapGenrePtr genre ); void setYear( DaapYearPtr year ); void setLength( qint64 length ); private: Collections::DaapCollection *m_collection; DaapArtistPtr m_artist; DaapAlbumPtr m_album; DaapGenrePtr m_genre; DaapComposerPtr m_composer; DaapYearPtr m_year; QString m_name; QString m_type; qint64 m_length; int m_trackNumber; QString m_displayUrl; QString m_playableUrl; }; class DaapArtist : public Meta::Artist { public: DaapArtist( const QString &name ); virtual ~DaapArtist(); virtual QString name() const; virtual TrackList tracks(); virtual AlbumList albums(); //DaapArtist specific methods void addTrack( DaapTrackPtr track ); private: QString m_name; TrackList m_tracks; }; class DaapAlbum : public Meta::Album { public: DaapAlbum( const QString &name ); virtual ~DaapAlbum(); virtual QString name() const; virtual bool isCompilation() const; virtual bool hasAlbumArtist() const; virtual ArtistPtr albumArtist() const; virtual TrackList tracks(); //DaapAlbum specific methods void addTrack( DaapTrackPtr track ); void setAlbumArtist( DaapArtistPtr artist ); private: QString m_name; TrackList m_tracks; bool m_isCompilation; DaapArtistPtr m_albumArtist; }; class DaapGenre : public Meta::Genre { public: DaapGenre( const QString &name ); virtual ~DaapGenre(); virtual QString name() const; virtual TrackList tracks(); //DaapGenre specific methods void addTrack( DaapTrackPtr track ); private: QString m_name; TrackList m_tracks; }; class DaapComposer : public Meta::Composer { public: DaapComposer( const QString &name ); virtual ~DaapComposer(); virtual QString name() const; virtual TrackList tracks(); //DaapComposer specific methods void addTrack( DaapTrackPtr track ); private: QString m_name; TrackList m_tracks; }; class DaapYear : public Meta::Year { public: DaapYear( const QString &name ); virtual ~DaapYear(); virtual QString name() const; virtual TrackList tracks(); //DaapYear specific methods void addTrack( DaapTrackPtr track ); private: QString m_name; TrackList m_tracks; }; } #endif diff --git a/src/core-impl/collections/daap/daapreader/Reader.h b/src/core-impl/collections/daap/daapreader/Reader.h index 331e89acb9..58b8440c21 100644 --- a/src/core-impl/collections/daap/daapreader/Reader.h +++ b/src/core-impl/collections/daap/daapreader/Reader.h @@ -1,142 +1,142 @@ /**************************************************************************************** * Copyright (c) 2006 Ian Monroe * * * * 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) version 3 or * * any later version accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * 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, see . * ****************************************************************************************/ #ifndef DAAPREADER_H #define DAAPREADER_H #include -#include +#include #include #include "MemoryCollection.h" class QString; namespace Collections { class DaapCollection; } class QHttpResponseHeader; namespace Daap { typedef QMap Map; enum ContentTypes { INVALID = 0, CHAR = 1, SHORT = 3, LONG = 5, LONGLONG = 7, STRING = 9, DATE = 10, DVERSION = 11, CONTAINER = 12 }; struct Code { Code() : type(INVALID) { } Code( const QString& nName, ContentTypes nType ) : name( nName ), type( nType ) { } ~Code() { } QString name; ContentTypes type; }; /** The nucleus of the DAAP client; composes queries and parses responses. @author Ian Monroe */ class Reader : public QObject { Q_OBJECT public: Reader( Collections::DaapCollection *mc, const QString& host, quint16 port, const QString& password, QObject* parent, const char* name ); ~Reader(); //QPtrList getSongList(); enum Options { SESSION_ID = 1, SERVER_VERSION = 2 }; void loginRequest(); void logoutRequest(); int sessionId() const { return m_sessionId; } QString host() const { return m_host; } quint16 port() const { return m_port; } bool parseSongList( const QByteArray &data, bool set_collection = false); public slots: void logoutRequest(int, bool ); void contentCodesReceived( int id , bool error ); void loginHeaderReceived( const QHttpResponseHeader& resp ); void loginFinished( int id , bool error ); void updateFinished( int id , bool error ); void databaseIdFinished( int id , bool error ); void songListFinished( int id, bool error ); void fetchingError( const QString& error ); signals: //void daapBundles( const QString& host, Daap::SongList bundles ); void httpError( const QString& ); void passwordRequired(); private: /** * Make a map-vector tree out of the DAAP binary result * @param raw stream of DAAP reply * @param containerLength length of the container (or entire result) being analyzed */ Map parse( QDataStream &raw); static void addElement( Map &parentMap, char* tag, QVariant element ); //!< supporter function for parse static quint32 getTagAndLength( QDataStream &raw, char tag[5] ); QVariant readTagData(QDataStream &, char[5], quint32); void addTrack( const QString& itemId, const QString& title, const QString& artist, const QString& composer, const QString& commment, const QString& album, const QString& genre, int year, const QString& format, qint32 trackNumber, qint32 songTime ); QMap m_codes; Collections::DaapCollection *m_memColl; QString m_host; quint16 m_port; QString m_loginString; QString m_databaseId; int m_sessionId; QString m_password; TrackMap m_trackMap; ArtistMap m_artistMap; AlbumMap m_albumMap; GenreMap m_genreMap; ComposerMap m_composerMap; YearMap m_yearMap; }; class WorkerThread : public ThreadWeaver::Job { Q_OBJECT public: WorkerThread( const QByteArray &data, Reader* reader, Collections::DaapCollection *coll ); virtual ~WorkerThread(); virtual bool success() const; protected: virtual void run(); private: bool m_success; QByteArray m_data; Reader *m_reader; }; } #endif diff --git a/src/core-impl/collections/db/DatabaseCollection.cpp b/src/core-impl/collections/db/DatabaseCollection.cpp index 705892ad6d..64f6453dea 100644 --- a/src/core-impl/collections/db/DatabaseCollection.cpp +++ b/src/core-impl/collections/db/DatabaseCollection.cpp @@ -1,249 +1,249 @@ /**************************************************************************************** * Copyright (c) 2007 Maximilian Kossick * * Copyright (c) 2007 Casey Link * * Copyright (c) 2010 Jeff Mitchell * * Copyright (c) 2013 Ralf Engels * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "DatabaseCollection" #include "DatabaseCollection.h" #include "core/support/Debug.h" #include "scanner/GenericScanManager.h" #include "MountPointManager.h" using namespace Collections; DatabaseCollection::DatabaseCollection() : Collection() , m_mpm( 0 ) , m_scanManager( 0 ) , m_blockUpdatedSignalCount( 0 ) , m_updatedSignalRequested( false ) { } DatabaseCollection::~DatabaseCollection() { delete m_mpm; } QString DatabaseCollection::collectionId() const { return QLatin1String( "localCollection" ); } QString DatabaseCollection::prettyName() const { return i18n( "Local Collection" ); } KIcon DatabaseCollection::icon() const { return KIcon("drive-harddisk"); } GenericScanManager* DatabaseCollection::scanManager() const { return m_scanManager; } MountPointManager* DatabaseCollection::mountPointManager() const { Q_ASSERT( m_mpm ); return m_mpm; } void DatabaseCollection::setMountPointManager( MountPointManager *mpm ) { Q_ASSERT( mpm ); if( m_mpm ) { disconnect( mpm, SIGNAL(deviceAdded(int)), this, SLOT(slotDeviceAdded(int)) ); disconnect( mpm, SIGNAL(deviceRemoved(int)), this, SLOT(slotDeviceRemoved(int)) ); } m_mpm = mpm; connect( mpm, SIGNAL(deviceAdded(int)), this, SLOT(slotDeviceAdded(int)) ); connect( mpm, SIGNAL(deviceRemoved(int)), this, SLOT(slotDeviceRemoved(int)) ); } QStringList DatabaseCollection::collectionFolders() const { return mountPointManager()->collectionFolders(); } void DatabaseCollection::setCollectionFolders( const QStringList &folders ) { mountPointManager()->setCollectionFolders( folders ); } void DatabaseCollection::blockUpdatedSignal() { QMutexLocker locker( &m_mutex ); m_blockUpdatedSignalCount ++; } void DatabaseCollection::unblockUpdatedSignal() { QMutexLocker locker( &m_mutex ); Q_ASSERT( m_blockUpdatedSignalCount > 0 ); m_blockUpdatedSignalCount --; // check if meanwhile somebody had updated the collection if( m_blockUpdatedSignalCount == 0 && m_updatedSignalRequested ) { m_updatedSignalRequested = false; locker.unlock(); emit updated(); } } void DatabaseCollection::collectionUpdated() { QMutexLocker locker( &m_mutex ); if( m_blockUpdatedSignalCount == 0 ) { m_updatedSignalRequested = false; locker.unlock(); emit updated(); } else { m_updatedSignalRequested = true; } } bool DatabaseCollection::hasCapabilityInterface( Capabilities::Capability::Type type ) const { switch( type ) { case Capabilities::Capability::CollectionImport: case Capabilities::Capability::CollectionScan: return true; default: return Collection::hasCapabilityInterface( type ); } } Capabilities::Capability* DatabaseCollection::createCapabilityInterface( Capabilities::Capability::Type type ) { switch( type ) { case Capabilities::Capability::CollectionImport: return new DatabaseCollectionImportCapability( this ); case Capabilities::Capability::CollectionScan: return new DatabaseCollectionScanCapability( this ); default: return Collection::createCapabilityInterface( type ); } } // --------- DatabaseCollectionScanCapability ------------- DatabaseCollectionScanCapability::DatabaseCollectionScanCapability( DatabaseCollection* collection ) : m_collection( collection ) { Q_ASSERT( m_collection ); } DatabaseCollectionScanCapability::~DatabaseCollectionScanCapability() { } void DatabaseCollectionScanCapability::startFullScan() { - QList urls; + QList urls; foreach( const QString& path, m_collection->mountPointManager()->collectionFolders() ) - urls.append( KUrl::fromPath( path ) ); + urls.append( QUrl::fromLocalFile( path ) ); m_collection->scanManager()->requestScan( urls, GenericScanManager::FullScan ); } void DatabaseCollectionScanCapability::startIncrementalScan( const QString &directory ) { if( directory.isEmpty() ) { - QList urls; + QList urls; foreach( const QString& path, m_collection->mountPointManager()->collectionFolders() ) - urls.append( KUrl::fromPath( path ) ); + urls.append( QUrl::fromLocalFile( path ) ); m_collection->scanManager()->requestScan( urls, GenericScanManager::UpdateScan ); } else { - QList urls; - urls.append( KUrl::fromPath( directory ) ); + QList urls; + urls.append( QUrl::fromLocalFile( directory ) ); m_collection->scanManager()->requestScan( urls, GenericScanManager::PartialUpdateScan ); } } void DatabaseCollectionScanCapability::stopScan() { m_collection->scanManager()->abort(); } // --------- DatabaseCollectionImportCapability ------------- DatabaseCollectionImportCapability::DatabaseCollectionImportCapability( DatabaseCollection* collection ) : m_collection( collection ) { Q_ASSERT( m_collection ); } DatabaseCollectionImportCapability::~DatabaseCollectionImportCapability() { } void DatabaseCollectionImportCapability::import( QIODevice *input, QObject *listener ) { DEBUG_BLOCK if( listener ) { // TODO: change import capability to collection action // TODO: why have listeners here and not for the scan capability // TODO: showMessage does not longer work like this, the scan result processor is doing this connect( m_collection->scanManager(), SIGNAL(succeeded()), listener, SIGNAL(importSucceeded()) ); connect( m_collection->scanManager(), SIGNAL(failed(QString)), listener, SIGNAL(showMessage(QString)) ); } m_collection->scanManager()->requestImport( input ); } diff --git a/src/core-impl/collections/db/MountPointManager.cpp b/src/core-impl/collections/db/MountPointManager.cpp index 77a43c0bd6..df5e9ccbf5 100644 --- a/src/core-impl/collections/db/MountPointManager.cpp +++ b/src/core-impl/collections/db/MountPointManager.cpp @@ -1,416 +1,418 @@ /**************************************************************************************** * Copyright (c) 2006-2007 Maximilian Kossick * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "MountPointManager" #include "MountPointManager.h" #include "MediaDeviceCache.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include #include "core-impl/collections/db/sql/device/massstorage/MassStorageDeviceHandler.h" #include "core-impl/collections/db/sql/device/nfs/NfsDeviceHandler.h" #include "core-impl/collections/db/sql/device/smb/SmbDeviceHandler.h" #include #include #include #include #include #include #include #include #include MountPointManager::MountPointManager( QObject *parent, SqlStorage *storage ) : QObject( parent ) , m_storage( storage ) , m_ready( false ) { DEBUG_BLOCK setObjectName( "MountPointManager" ); if ( !Amarok::config( "Collection" ).readEntry( "DynamicCollection", true ) ) { debug() << "Dynamic Collection deactivated in amarokrc, not loading plugins, not connecting signals"; m_ready = true; handleMusicLocation(); return; } connect( MediaDeviceCache::instance(), SIGNAL(deviceAdded(QString)), SLOT(deviceAdded(QString)) ); connect( MediaDeviceCache::instance(), SIGNAL(deviceRemoved(QString)), SLOT(deviceRemoved(QString)) ); createDeviceFactories(); } void MountPointManager::handleMusicLocation() { // For users who were using QDesktopServices::MusicLocation exclusively up // to v2.2.2, which did not store the location into config. // and also for versions up to 2.7-git that did wrote the Use MusicLocation entry KConfigGroup folders = Amarok::config( "Collection Folders" ); const QString entryKey( "Use MusicLocation" ); if( !folders.hasKey( entryKey ) ) return; // good, already solved, nothing to do // write the music location as another collection folder in this case if( folders.readEntry( entryKey, false ) ) { - const KUrl musicUrl = QDesktopServices::storageLocation( QDesktopServices::MusicLocation ); - const QString musicDir = musicUrl.toLocalFile( KUrl::RemoveTrailingSlash ); + const QUrl musicUrl = QDesktopServices::storageLocation( QDesktopServices::MusicLocation ); + const QString musicDir = musicUrl.toLocalFile( QUrl::RemoveTrailingSlash ); const QDir dir( musicDir ); if( dir.exists() && dir.isReadable() ) { QStringList currentFolders = collectionFolders(); if( !currentFolders.contains( musicDir ) ) setCollectionFolders( currentFolders << musicDir ); } } folders.deleteEntry( entryKey ); // get rid of it for good } MountPointManager::~MountPointManager() { DEBUG_BLOCK m_handlerMapMutex.lock(); foreach( DeviceHandler *dh, m_handlerMap ) delete dh; m_handlerMapMutex.unlock(); // DeviceHandlerFactories are memory managed using QObject parentship } void MountPointManager::createDeviceFactories() { DEBUG_BLOCK QList factories; factories << new MassStorageDeviceHandlerFactory( this ); factories << new NfsDeviceHandlerFactory( this ); factories << new SmbDeviceHandlerFactory( this ); foreach( DeviceHandlerFactory *factory, factories ) { debug() << "Initializing DeviceHandlerFactory of type:" << factory->type(); if( factory->canCreateFromMedium() ) m_mediumFactories.append( factory ); else if (factory->canCreateFromConfig() ) m_remoteFactories.append( factory ); else //FIXME max: better error message debug() << "Unknown DeviceHandlerFactory"; } Solid::Predicate predicate = Solid::Predicate( Solid::DeviceInterface::StorageAccess ); QList devices = Solid::Device::listFromQuery( predicate ); foreach( const Solid::Device &device, devices ) createHandlerFromDevice( device, device.udi() ); m_ready = true; handleMusicLocation(); } int -MountPointManager::getIdForUrl( const KUrl &url ) +MountPointManager::getIdForUrl( const QUrl &url ) { int mountPointLength = 0; int id = -1; m_handlerMapMutex.lock(); foreach( DeviceHandler *dh, m_handlerMap ) { if ( url.path().startsWith( dh->getDevicePath() ) && mountPointLength < dh->getDevicePath().length() ) { id = m_handlerMap.key( dh ); mountPointLength = dh->getDevicePath().length(); } } m_handlerMapMutex.unlock(); if ( mountPointLength > 0 ) { return id; } else { //default fallback if we could not identify the mount point. //treat -1 as mount point / in all other methods return -1; } } bool MountPointManager::isMounted( const int deviceId ) const { m_handlerMapMutex.lock(); const bool result = m_handlerMap.contains( deviceId ); m_handlerMapMutex.unlock(); return result; } QString MountPointManager::getMountPointForId( const int id ) const { QString mountPoint; if ( isMounted( id ) ) { m_handlerMapMutex.lock(); mountPoint = m_handlerMap[id]->getDevicePath(); m_handlerMapMutex.unlock(); } else //TODO better error handling mountPoint = '/'; return mountPoint; } QString MountPointManager::getAbsolutePath( const int deviceId, const QString& relativePath ) const { - // TODO: someone who clearly understands KUrl should clean this up. - KUrl rpath; + // TODO: someone who clearly understands QUrl should clean this up. + QUrl rpath; rpath.setPath( relativePath ); - KUrl absolutePath; + QUrl absolutePath; // debug() << "id is " << deviceId << ", relative path is " << relativePath; if ( deviceId == -1 ) { #ifdef Q_OS_WIN32 absolutePath.setPath( rpath.toLocalFile() ); #else absolutePath.setPath( "/" ); - absolutePath.addPath( rpath.path() ); + absolutePath = absolutePath.adjusted(QUrl::StripTrailingSlash); + absolutePath.setPath(absolutePath.path() + '/' + ( rpath.path() )); #endif absolutePath.cleanPath(); // debug() << "Deviceid is -1, using relative Path as absolute Path, returning " << absolutePath.path(); } else { m_handlerMapMutex.lock(); if ( m_handlerMap.contains( deviceId ) ) { m_handlerMap[deviceId]->getURL( absolutePath, rpath ); m_handlerMapMutex.unlock(); } else { m_handlerMapMutex.unlock(); const QStringList lastMountPoint = m_storage->query( QString( "SELECT lastmountpoint FROM devices WHERE id = %1" ) .arg( deviceId ) ); if ( lastMountPoint.count() == 0 ) { //hmm, no device with that id in the DB...serious problem warning() << "Device " << deviceId << " not in database, this should never happen!"; return getAbsolutePath( -1, relativePath ); } else { absolutePath.setPath( lastMountPoint.first() ); - absolutePath.addPath( rpath.path() ); + absolutePath = absolutePath.adjusted(QUrl::StripTrailingSlash); + absolutePath.setPath(absolutePath.path() + '/' + ( rpath.path() )); absolutePath.cleanPath(); //debug() << "Device " << deviceId << " not mounted, using last mount point and returning " << absolutePath.path(); } } } #ifdef Q_OS_WIN32 return absolutePath.toLocalFile(); #else return absolutePath.path(); #endif } QString MountPointManager::getRelativePath( const int deviceId, const QString& absolutePath ) const { QMutexLocker locker(&m_handlerMapMutex); if ( deviceId != -1 && m_handlerMap.contains( deviceId ) ) { //FIXME max: returns garbage if the absolute path is actually not under the device's mount point - return KUrl::relativePath( m_handlerMap[deviceId]->getDevicePath(), absolutePath ); + return QUrl::relativePath( m_handlerMap[deviceId]->getDevicePath(), absolutePath ); } else { //TODO: better error handling #ifdef Q_OS_WIN32 - return KUrl( absolutePath ).toLocalFile(); + return QUrl( absolutePath ).toLocalFile(); #else - return KUrl::relativePath( "/", absolutePath ); + return QUrl::relativePath( "/", absolutePath ); #endif } } IdList MountPointManager::getMountedDeviceIds() const { m_handlerMapMutex.lock(); IdList list( m_handlerMap.keys() ); m_handlerMapMutex.unlock(); list.append( -1 ); return list; } QStringList MountPointManager::collectionFolders() const { if( !m_ready ) { debug() << "requested collectionFolders from MountPointManager that is not yet ready"; return QStringList(); } //TODO max: cache data QStringList result; KConfigGroup folders = Amarok::config( "Collection Folders" ); const IdList ids = getMountedDeviceIds(); foreach( int id, ids ) { const QStringList rpaths = folders.readEntry( QString::number( id ), QStringList() ); foreach( const QString &strIt, rpaths ) { - const KUrl url = ( strIt == "./" ) ? getMountPointForId( id ) : getAbsolutePath( id, strIt ); - const QString absPath = url.toLocalFile( KUrl::RemoveTrailingSlash ); + const QUrl url = ( strIt == "./" ) ? getMountPointForId( id ) : getAbsolutePath( id, strIt ); + const QString absPath = url.toLocalFile( QUrl::RemoveTrailingSlash ); if ( !result.contains( absPath ) ) result.append( absPath ); } } return result; } void MountPointManager::setCollectionFolders( const QStringList &folders ) { typedef QMap FolderMap; KConfigGroup folderConf = Amarok::config( "Collection Folders" ); FolderMap folderMap; foreach( const QString &folder, folders ) { int id = getIdForUrl( folder ); const QString rpath = getRelativePath( id, folder ); if( folderMap.contains( id ) ) { if( !folderMap[id].contains( rpath ) ) folderMap[id].append( rpath ); } else folderMap[id] = QStringList( rpath ); } //make sure that collection folders on devices which are not in foldermap are deleted IdList ids = getMountedDeviceIds(); foreach( int deviceId, ids ) { if( !folderMap.contains( deviceId ) ) { folderConf.deleteEntry( QString::number( deviceId ) ); } } QMapIterator i( folderMap ); while( i.hasNext() ) { i.next(); folderConf.writeEntry( QString::number( i.key() ), i.value() ); } } void MountPointManager::deviceAdded( const QString &udi ) { DEBUG_BLOCK Solid::Predicate predicate = Solid::Predicate( Solid::DeviceInterface::StorageAccess ); QList devices = Solid::Device::listFromQuery( predicate ); //Looking for a specific udi in predicate seems flaky/buggy; the foreach loop barely //takes any time, so just be safe bool found = false; debug() << "looking for udi " << udi; foreach( const Solid::Device &device, devices ) { if( device.udi() == udi ) { createHandlerFromDevice( device, udi ); found = true; } } if( !found ) debug() << "Did not find device from Solid for udi " << udi; } void MountPointManager::deviceRemoved( const QString &udi ) { DEBUG_BLOCK m_handlerMapMutex.lock(); foreach( DeviceHandler *dh, m_handlerMap ) { if( dh->deviceMatchesUdi( udi ) ) { int key = m_handlerMap.key( dh ); m_handlerMap.remove( key ); delete dh; debug() << "removed device " << key; m_handlerMapMutex.unlock(); //we found the medium which was removed, so we can abort the loop emit deviceRemoved( key ); return; } } m_handlerMapMutex.unlock(); } void MountPointManager::createHandlerFromDevice( const Solid::Device& device, const QString &udi ) { DEBUG_BLOCK if ( device.isValid() ) { debug() << "Device added and mounted, checking handlers"; foreach( DeviceHandlerFactory *factory, m_mediumFactories ) { if( factory->canHandle( device ) ) { debug() << "found handler for " << udi; DeviceHandler *handler = factory->createHandler( device, udi, m_storage ); if( !handler ) { debug() << "Factory " << factory->type() << "could not create device handler"; break; } int key = handler->getDeviceID(); m_handlerMapMutex.lock(); if( m_handlerMap.contains( key ) ) { debug() << "Key " << key << " already exists in handlerMap, replacing"; delete m_handlerMap[key]; m_handlerMap.remove( key ); } m_handlerMap.insert( key, handler ); m_handlerMapMutex.unlock(); // debug() << "added device " << key << " with mount point " << volumeAccess->mountPoint(); emit deviceAdded( key ); break; //we found the added medium and don't have to check the other device handlers } else debug() << "Factory can't handle device " << udi; } } else debug() << "Device not valid!"; } diff --git a/src/core-impl/collections/db/MountPointManager.h b/src/core-impl/collections/db/MountPointManager.h index 4e588d7a8e..36dc6536b3 100644 --- a/src/core-impl/collections/db/MountPointManager.h +++ b/src/core-impl/collections/db/MountPointManager.h @@ -1,231 +1,231 @@ /**************************************************************************************** * Copyright (c) 2006-2007 Maximilian Kossick * * * * 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, see . * ****************************************************************************************/ #ifndef AMAROK_MOUNTPOINTMANAGER_H #define AMAROK_MOUNTPOINTMANAGER_H #include "core-impl/collections/db/sql/amarok_sqlcollection_export.h" #include #include #include #include class DeviceHandler; class DeviceHandlerFactory; class SqlStorage; -class KUrl; +class QUrl; namespace Solid { class Device; } typedef QList IdList; typedef QList FactoryList; typedef QMap HandlerMap; class DeviceHandlerFactory : public QObject { Q_OBJECT public: DeviceHandlerFactory( QObject *parent ) : QObject( parent ) {} virtual ~DeviceHandlerFactory() {} /** * checks whether a DeviceHandler subclass can handle a given Medium. * @param volume the connected solid volume * @return true if the DeviceHandler implementation can handle the medium, * false otherwise */ virtual bool canHandle( const Solid::Device &device ) const = 0; /** * tells the MountPointManager whether it makes sense to ask the factory to * create a Devicehandler when a new Medium was connected * @return true if the factory can create DeviceHandlers from Medium instances */ virtual bool canCreateFromMedium() const = 0; /** * creates a DeviceHandler which represents the Medium. * @param volume the Volume for which a DeviceHandler is required * @return a DeviceHandler or 0 if the factory cannot handle the Medium */ virtual DeviceHandler* createHandler( const Solid::Device &device, const QString &udi, SqlStorage *s ) const = 0; virtual bool canCreateFromConfig() const = 0; virtual DeviceHandler* createHandler( KSharedConfigPtr c, SqlStorage *s ) const = 0; /** * returns the type of the DeviceHandler. Should be the same as the value used in * ~/.kde/share/config/amarokrc * @return a QString describing the type of the DeviceHandler */ virtual QString type() const = 0; }; class DeviceHandler { public: DeviceHandler() {} virtual ~DeviceHandler() {} virtual bool isAvailable() const = 0; /** * returns the type of the DeviceHandler. Should be the same as the value used in * ~/.kde/share/config/amarokrc * @return a QString describing the type of the DeviceHandler */ virtual QString type() const = 0; /** * returns an absolute path which is guaranteed to be playable by amarok's current engine. (based on an * idea by andrewt512: this method would only be called when we actually want to play the file, not when we * simply want to show it to the user. It could for example download a file using KIO and return a path to a * temporary file. Needs some more thought and is not actually used at the moment. * @param absolutePath * @param relativePath */ - virtual void getPlayableURL( KUrl &absolutePath, const KUrl &relativePath ) = 0; + virtual void getPlayableURL( QUrl &absolutePath, const QUrl &relativePath ) = 0; /** * builds an absolute path from a relative path and DeviceHandler specific information. The absolute path * is not necessarily playable! (based on an idea by andrewt512: allows better handling of files stored in remote * collections. this method would return a "pretty" URL which might not be playable by amarok's engines. * @param absolutePath the not necessarily playbale absolute path * @param relativePath the device specific relative path */ - virtual void getURL( KUrl &absolutePath, const KUrl &relativePath ) = 0; + virtual void getURL( QUrl &absolutePath, const QUrl &relativePath ) = 0; /** * retrieves the unique database id of a given Medium. Implementations are responsible * for generating a (sufficiently) unique value which identifies the Medium. * Additionally, implementations must recognize unknown mediums and store the necessary * information to recognize them the next time they are connected in the database. * @return unique identifier which can be used as a foreign key to the media table. */ virtual int getDeviceID() = 0; virtual const QString &getDevicePath() const = 0; /** * allows MountPointManager to check if a device handler handles a specific medium. * @param m * @return true if the device handler handles the Medium m */ virtual bool deviceMatchesUdi( const QString &udi ) const = 0; }; /** * @author Maximilian Kossick */ class AMAROK_SQLCOLLECTION_EXPORT MountPointManager : public QObject { Q_OBJECT public: MountPointManager( QObject *parent, SqlStorage *storage ); ~MountPointManager(); /** * * @param url * @return */ - virtual int getIdForUrl( const KUrl &url ); + virtual int getIdForUrl( const QUrl &url ); /** * * @param id * @return */ virtual QString getMountPointForId( const int id ) const; /** * builds the absolute path from the mount point of the medium and the given relative * path. * @param deviceId the medium(device)'s unique id * @param relativePath relative path on the medium * @return the absolute path */ virtual QString getAbsolutePath( const int deviceId, const QString& relativePath ) const; /** * calculates a file's/directory's relative path on a given device. * @param deviceId the unique id which identifies the device the file/directory is supposed to be on * @param absolutePath the file's/directory's absolute path * @param relativePath the calculated relative path */ virtual QString getRelativePath( const int deviceId, const QString& absolutePath ) const; /** * allows calling code to access the ids of all active devices * @return the ids of all devices which are currently mounted or otherwise accessible */ virtual IdList getMountedDeviceIds() const; virtual QStringList collectionFolders() const; virtual void setCollectionFolders( const QStringList &folders ); signals: void deviceAdded( int id ); void deviceRemoved( int id ); private: void createDeviceFactories(); /** * Old Amarok versions used to have "Use MusicLocation" config option which caused * problems (bug 316216). This method converts out of it and needs to be run after * MountPointManager has initialized. */ void handleMusicLocation(); /** * checks whether a medium identified by its unique database id is currently mounted. * Note: does not handle deviceId = -1! It only checks real devices * @param deviceId the mediums unique id * @return true if the medium is mounted, false otherwise */ bool isMounted ( const int deviceId ) const; SqlStorage *m_storage; /** * maps a device id to a mount point. does only work for mountable filesystems and needs to be * changed for the real Dynamic Collection implementation. */ HandlerMap m_handlerMap; mutable QMutex m_handlerMapMutex; FactoryList m_mediumFactories; FactoryList m_remoteFactories; bool m_ready; //Solid specific void createHandlerFromDevice( const Solid::Device &device, const QString &udi ); private slots: void deviceAdded( const QString &udi ); void deviceRemoved( const QString &udi ); }; #define AMAROK_EXPORT_DEVICE_PLUGIN(libname, classname) \ K_PLUGIN_FACTORY(factory, registerPlugin();) \ K_EXPORT_PLUGIN(factory("amarok_device_" #libname))\ #endif diff --git a/src/core-impl/collections/db/sql/SqlCollection.cpp b/src/core-impl/collections/db/sql/SqlCollection.cpp index 2fef3ea673..5d125fb2b2 100644 --- a/src/core-impl/collections/db/sql/SqlCollection.cpp +++ b/src/core-impl/collections/db/sql/SqlCollection.cpp @@ -1,471 +1,471 @@ /**************************************************************************************** * Copyright (c) 2007 Maximilian Kossick * * Copyright (c) 2007 Casey Link * * Copyright (c) 2008-2009 Jeff Mitchell * * Copyright (c) 2013 Ralf Engels * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "SqlCollection" #include "SqlCollection.h" #include "DefaultSqlQueryMakerFactory.h" #include "DatabaseUpdater.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include "core/capabilities/TranscodeCapability.h" #include "core/transcoding/TranscodingController.h" #include "core-impl/collections/db/MountPointManager.h" #include "scanner/GenericScanManager.h" #include "scanner/AbstractDirectoryWatcher.h" #include "dialogs/OrganizeCollectionDialog.h" #include "SqlCollectionLocation.h" #include "SqlQueryMaker.h" #include "SqlScanResultProcessor.h" #include "SvgHandler.h" #include "MainWindow.h" #include "collectionscanner/BatchFile.h" #include #include #include #include #include /** Concrete implementation of the directory watcher */ class SqlDirectoryWatcher : public AbstractDirectoryWatcher { public: SqlDirectoryWatcher( Collections::SqlCollection* collection ) : AbstractDirectoryWatcher() , m_collection( collection ) { } ~SqlDirectoryWatcher() { } protected: QList collectionFolders() { return m_collection->mountPointManager()->collectionFolders(); } Collections::SqlCollection* m_collection; }; class SqlScanManager : public GenericScanManager { public: SqlScanManager( Collections::SqlCollection* collection, QObject* parent ) : GenericScanManager( parent ) , m_collection( collection ) { } ~SqlScanManager() { } protected: QList< QPair > getKnownDirs() { QList< QPair > result; // -- get all (mounted) mount points QList idList = m_collection->mountPointManager()->getMountedDeviceIds(); // -- query all known directories QString deviceIds; foreach( int id, idList ) { if( !deviceIds.isEmpty() ) deviceIds += ','; deviceIds += QString::number( id ); } QString query = QString( "SELECT deviceid, dir, changedate FROM directories WHERE deviceid IN (%1);" ); QStringList values = m_collection->sqlStorage()->query( query.arg( deviceIds ) ); for( QListIterator iter( values ); iter.hasNext(); ) { int deviceid = iter.next().toInt(); QString dir = iter.next(); uint mtime = iter.next().toUInt(); QString folder = m_collection->mountPointManager()->getAbsolutePath( deviceid, dir ); result.append( QPair( folder, mtime ) ); } return result; } QString getBatchFile( const QStringList &scanDirsRequested ) { // -- write the batch file // the batch file contains the known modification dates so that the scanner only // needs to report changed directories QList > knownDirs = getKnownDirs(); if( !knownDirs.isEmpty() ) { QString path = KGlobal::dirs()->saveLocation( "data", QString("amarok/"), false ) + "amarokcollectionscanner_batchscan.xml"; while( QFile::exists( path ) ) path += '_'; CollectionScanner::BatchFile batchfile; batchfile.setTimeDefinitions( knownDirs ); batchfile.setDirectories( scanDirsRequested ); if( !batchfile.write( path ) ) { warning() << "Failed to write batch file" << path; return QString(); } return path; } return QString(); } Collections::SqlCollection* m_collection; }; namespace Collections { class OrganizeCollectionDelegateImpl : public OrganizeCollectionDelegate { public: OrganizeCollectionDelegateImpl() : OrganizeCollectionDelegate() , m_dialog( 0 ) , m_organizing( false ) {} virtual ~ OrganizeCollectionDelegateImpl() { delete m_dialog; } virtual void setTracks( const Meta::TrackList &tracks ) { m_tracks = tracks; } virtual void setFolders( const QStringList &folders ) { m_folders = folders; } virtual void setIsOrganizing( bool organizing ) { m_organizing = organizing; } virtual void setTranscodingConfiguration( const Transcoding::Configuration &configuration ) { m_targetFileExtension = Amarok::Components::transcodingController()->format( configuration.encoder() )->fileExtension(); } virtual void setCaption( const QString &caption ) { m_caption = caption; } virtual void show() { m_dialog = new OrganizeCollectionDialog( m_tracks, m_folders, m_targetFileExtension, The::mainWindow(), //parent "", //name is unused true, //modal m_caption //caption ); connect( m_dialog, SIGNAL(accepted()), SIGNAL(accepted()) ); connect( m_dialog, SIGNAL(rejected()), SIGNAL(rejected()) ); m_dialog->show(); } virtual bool overwriteDestinations() const { return m_dialog->overwriteDestinations(); } virtual QMap destinations() const { return m_dialog->getDestinations(); } private: Meta::TrackList m_tracks; QStringList m_folders; OrganizeCollectionDialog *m_dialog; bool m_organizing; QString m_targetFileExtension; QString m_caption; }; class OrganizeCollectionDelegateFactoryImpl : public OrganizeCollectionDelegateFactory { public: virtual OrganizeCollectionDelegate* createDelegate() { return new OrganizeCollectionDelegateImpl(); } }; class SqlCollectionLocationFactoryImpl : public SqlCollectionLocationFactory { public: SqlCollectionLocationFactoryImpl( SqlCollection *collection ) : SqlCollectionLocationFactory() , m_collection( collection ) {} SqlCollectionLocation *createSqlCollectionLocation() const { Q_ASSERT( m_collection ); SqlCollectionLocation *loc = new SqlCollectionLocation( m_collection ); loc->setOrganizeCollectionDelegateFactory( new OrganizeCollectionDelegateFactoryImpl() ); return loc; } Collections::SqlCollection *m_collection; }; } //namespace Collections using namespace Collections; SqlCollection::SqlCollection( SqlStorage* storage ) : DatabaseCollection() , m_registry( 0 ) , m_sqlStorage( storage ) , m_scanProcessor( 0 ) , m_directoryWatcher( 0 ) , m_collectionLocationFactory( 0 ) , m_queryMakerFactory( 0 ) { qRegisterMetaType( "TrackUrls" ); qRegisterMetaType( "ChangedTrackUrls" ); // update database to current schema version; this must be run *before* MountPointManager // is initialized or its handlers may try to insert // into the database before it's created/updated! DatabaseUpdater updater( this ); if( updater.needsUpdate() ) { if( updater.schemaExists() ) // this is an update { KDialog dialog( 0, Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint ); QLabel label( i18n( "Updating Amarok database schema. Please don't terminate " "Amarok now as it may result in database corruption." ) ); label.setWordWrap( true ); dialog.setMainWidget( &label ); dialog.setCaption( i18n( "Updating Amarok database schema" ) ); dialog.setButtons( KDialog::None ); dialog.setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); dialog.show(); dialog.raise(); // otherwise the splash screen doesn't load image and this dialog is not shown: kapp->processEvents(); updater.update(); dialog.hide(); kapp->processEvents(); } else // this is new schema creation updater.update(); } //perform a quick check of the database updater.cleanupDatabase(); m_registry = new SqlRegistry( this ); m_collectionLocationFactory = new SqlCollectionLocationFactoryImpl( this ); m_queryMakerFactory = new DefaultSqlQueryMakerFactory( this ); // scanning m_scanManager = new SqlScanManager( this, this ); m_scanProcessor = new SqlScanResultProcessor( m_scanManager, this, this ); m_directoryWatcher = new SqlDirectoryWatcher( this ); connect( m_directoryWatcher, SIGNAL(done(ThreadWeaver::Job*)), m_directoryWatcher, SLOT(deleteLater()) ); // auto delete - connect( m_directoryWatcher, SIGNAL(requestScan(QList,GenericScanManager::ScanType)), - m_scanManager, SLOT(requestScan(QList,GenericScanManager::ScanType)) ); + connect( m_directoryWatcher, SIGNAL(requestScan(QList,GenericScanManager::ScanType)), + m_scanManager, SLOT(requestScan(QList,GenericScanManager::ScanType)) ); ThreadWeaver::Weaver::instance()->enqueue( m_directoryWatcher ); } SqlCollection::~SqlCollection() { m_directoryWatcher->abort(); delete m_scanProcessor; // this prevents any further commits from the scanner delete m_collectionLocationFactory; delete m_queryMakerFactory; delete m_sqlStorage; delete m_registry; } QString SqlCollection::uidUrlProtocol() const { return "amarok-sqltrackuid"; } QString SqlCollection::generateUidUrl( const QString &hash ) { return uidUrlProtocol() + "://" + hash; } QueryMaker* SqlCollection::queryMaker() { Q_ASSERT( m_queryMakerFactory ); return m_queryMakerFactory->createQueryMaker(); } SqlRegistry* SqlCollection::registry() const { Q_ASSERT( m_registry ); return m_registry; } SqlStorage* SqlCollection::sqlStorage() const { Q_ASSERT( m_sqlStorage ); return m_sqlStorage; } bool -SqlCollection::possiblyContainsTrack( const KUrl &url ) const +SqlCollection::possiblyContainsTrack( const QUrl &url ) const { if( url.isLocalFile() ) { foreach( const QString &folder, collectionFolders() ) { - if( KUrl( folder ).isParentOf( url ) ) + if( QUrl( folder ).isParentOf( url ) ) return true; } return false; } else - return url.protocol() == uidUrlProtocol(); + return url.scheme() == uidUrlProtocol(); } Meta::TrackPtr -SqlCollection::trackForUrl( const KUrl &url ) +SqlCollection::trackForUrl( const QUrl &url ) { - if( url.protocol() == uidUrlProtocol() ) + if( url.scheme() == uidUrlProtocol() ) return m_registry->getTrackFromUid( url.url() ); - else if( url.protocol() == "file" ) + else if( url.scheme() == "file" ) return m_registry->getTrack( url.path() ); else return Meta::TrackPtr(); } Meta::TrackPtr SqlCollection::getTrack( int deviceId, const QString &rpath, int directoryId, const QString &uidUrl ) { return m_registry->getTrack( deviceId, rpath, directoryId, uidUrl ); } Meta::TrackPtr SqlCollection::getTrackFromUid( const QString &uniqueid ) { return m_registry->getTrackFromUid( uniqueid ); } Meta::AlbumPtr SqlCollection::getAlbum( const QString &album, const QString &artist ) { return m_registry->getAlbum( album, artist ); } CollectionLocation* SqlCollection::location() { Q_ASSERT( m_collectionLocationFactory ); return m_collectionLocationFactory->createSqlCollectionLocation(); } void SqlCollection::slotDeviceAdded( int id ) { QString query = "select count(*) from tracks inner join urls on tracks.url = urls.id where urls.deviceid = %1"; QStringList rs = m_sqlStorage->query( query.arg( id ) ); if( !rs.isEmpty() ) { int count = rs.first().toInt(); if( count > 0 ) { collectionUpdated(); } } else { warning() << "Query " << query << "did not return a result! Is the database available?"; } } void SqlCollection::slotDeviceRemoved( int id ) { QString query = "select count(*) from tracks inner join urls on tracks.url = urls.id where urls.deviceid = %1"; QStringList rs = m_sqlStorage->query( query.arg( id ) ); if( !rs.isEmpty() ) { int count = rs.first().toInt(); if( count > 0 ) { collectionUpdated(); } } else { warning() << "Query " << query << "did not return a result! Is the database available?"; } } bool SqlCollection::hasCapabilityInterface( Capabilities::Capability::Type type ) const { switch( type ) { case Capabilities::Capability::Transcode: return true; default: return DatabaseCollection::hasCapabilityInterface( type ); } } Capabilities::Capability* SqlCollection::createCapabilityInterface( Capabilities::Capability::Type type ) { switch( type ) { case Capabilities::Capability::Transcode: return new SqlCollectionTranscodeCapability(); default: return DatabaseCollection::createCapabilityInterface( type ); } } void SqlCollection::dumpDatabaseContent() { DatabaseUpdater updater( this ); QStringList tables = m_sqlStorage->query( "select table_name from INFORMATION_SCHEMA.tables WHERE table_schema='amarok'" ); foreach( const QString &table, tables ) { QString filePath = QDir::home().absoluteFilePath( table + '-' + QDateTime::currentDateTime().toString( Qt::ISODate ) + ".csv" ); updater.writeCSVFile( table, filePath, true ); } } // ---------- SqlCollectionTranscodeCapability ------------- SqlCollectionTranscodeCapability::~SqlCollectionTranscodeCapability() { // nothing to do } Transcoding::Configuration SqlCollectionTranscodeCapability::savedConfiguration() { KConfigGroup transcodeGroup = Amarok::config( SQL_TRANSCODING_GROUP_NAME ); return Transcoding::Configuration::fromConfigGroup( transcodeGroup ); } void SqlCollectionTranscodeCapability::setSavedConfiguration( const Transcoding::Configuration &configuration ) { KConfigGroup transcodeGroup = Amarok::config( SQL_TRANSCODING_GROUP_NAME ); configuration.saveToConfigGroup( transcodeGroup ); transcodeGroup.sync(); } diff --git a/src/core-impl/collections/db/sql/SqlCollection.h b/src/core-impl/collections/db/sql/SqlCollection.h index 7478c7956b..3d5a0b0c12 100644 --- a/src/core-impl/collections/db/sql/SqlCollection.h +++ b/src/core-impl/collections/db/sql/SqlCollection.h @@ -1,120 +1,120 @@ /**************************************************************************************** * Copyright (c) 2007 Maximilian Kossick * * Copyright (c) 2007 Casey Link * * Copyright (c) 2013 Ralf Engels * * * * 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, see . * ****************************************************************************************/ #ifndef AMAROK_COLLECTION_SQLCOLLECTION_H #define AMAROK_COLLECTION_SQLCOLLECTION_H #include "amarok_sqlcollection_export.h" #include "core/capabilities/TranscodeCapability.h" #include #include #include "SqlRegistry.h" class SqlScanResultProcessor; class AbstractDirectoryWatcher; namespace Collections { class CollectionLocation; class SqlCollectionLocationFactory; class SqlQueryMakerFactory; /// Configuration group name in amarokrc for preferred transcoding configuration for SqlCollection static const QString SQL_TRANSCODING_GROUP_NAME = "Collection Transcoding Preference"; class AMAROK_SQLCOLLECTION_EXPORT SqlCollection : public Collections::DatabaseCollection { Q_OBJECT public: /** Creates a new SqlCollection. * @param storage The storage this collection should work on. It will be freed by the collection. */ SqlCollection( SqlStorage *storage ); virtual ~SqlCollection(); virtual QueryMaker *queryMaker(); /** Returns the protocol for the uid urls of this collection. The SqlCollection support "amarok-sqltrackuid" and "file" protocol. */ virtual QString uidUrlProtocol() const; /** * Generates uidUrl out of a hash (as returned by tag reader) that can be then * fed to Track::setUidUrl(). */ QString generateUidUrl( const QString &hash ); // Local collection cannot have a capacity since it may be spread over multiple // physical locations (even network components) SqlRegistry* registry() const; SqlStorage* sqlStorage() const; /** Every collection has this function. */ - virtual bool possiblyContainsTrack( const KUrl &url ) const; + virtual bool possiblyContainsTrack( const QUrl &url ) const; - virtual Meta::TrackPtr trackForUrl( const KUrl &url ); + virtual Meta::TrackPtr trackForUrl( const QUrl &url ); /** Gets an existing track (or a new one) at the given position. This function should only be used by the SqlScanResultProcessor. */ virtual Meta::TrackPtr getTrack( int deviceId, const QString &rpath, int directoryId, const QString &uidUrl ); virtual Meta::TrackPtr getTrackFromUid( const QString &uniqueid ); virtual Meta::AlbumPtr getAlbum( const QString &album, const QString &artist ); virtual CollectionLocation* location(); virtual bool hasCapabilityInterface( Capabilities::Capability::Type type ) const; virtual Capabilities::Capability* createCapabilityInterface( Capabilities::Capability::Type type ); public slots: /** Dumps the complete database content. * The content of all Amarok tables is dumped in a couple of files * in the users homedirectory. */ void dumpDatabaseContent(); private slots: void slotDeviceAdded( int id ); void slotDeviceRemoved( int id ); private: SqlRegistry* m_registry; SqlStorage* m_sqlStorage; SqlScanResultProcessor* m_scanProcessor; AbstractDirectoryWatcher* m_directoryWatcher; SqlCollectionLocationFactory* m_collectionLocationFactory; SqlQueryMakerFactory* m_queryMakerFactory; }; class AMAROK_SQLCOLLECTION_EXPORT SqlCollectionTranscodeCapability : public Capabilities::TranscodeCapability { Q_OBJECT public: virtual ~SqlCollectionTranscodeCapability(); virtual Transcoding::Configuration savedConfiguration(); virtual void setSavedConfiguration( const Transcoding::Configuration &configuration ); }; } #endif /* AMAROK_COLLECTION_SQLCOLLECTION_H */ diff --git a/src/core-impl/collections/db/sql/SqlCollectionLocation.cpp b/src/core-impl/collections/db/sql/SqlCollectionLocation.cpp index 9b7cf0fdb8..317570b12c 100644 --- a/src/core-impl/collections/db/sql/SqlCollectionLocation.cpp +++ b/src/core-impl/collections/db/sql/SqlCollectionLocation.cpp @@ -1,783 +1,784 @@ /**************************************************************************************** * Copyright (c) 2007 Maximilian Kossick * * Copyright (c) 2008 Jason A. Donenfeld * * Copyright (c) 2010 Casey Link * * Copyright (c) 2010 Teo Mrnjavac * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "SqlCollectionLocation" #include "SqlCollectionLocation.h" #include "MetaTagLib.h" // for getting the uid #include "core/collections/CollectionLocationDelegate.h" #include #include "core/interfaces/Logger.h" #include "core/support/Components.h" #include "core/support/Debug.h" #include "core/meta/Meta.h" #include "core/meta/support/MetaUtility.h" #include "core/transcoding/TranscodingController.h" #include "core-impl/collections/db/MountPointManager.h" #include "core-impl/collections/db/sql/SqlCollection.h" #include "core-impl/collections/db/sql/SqlMeta.h" #include "transcoding/TranscodingJob.h" #include #include #include #include #include #include #include #include #include #include using namespace Collections; SqlCollectionLocation::SqlCollectionLocation( SqlCollection *collection ) : CollectionLocation( collection ) , m_collection( collection ) , m_delegateFactory( 0 ) , m_overwriteFiles( false ) , m_transferjob( 0 ) { //nothing to do } SqlCollectionLocation::~SqlCollectionLocation() { //nothing to do delete m_delegateFactory; } QString SqlCollectionLocation::prettyLocation() const { return i18n( "Local Collection" ); } QStringList SqlCollectionLocation::actualLocation() const { return m_collection->mountPointManager()->collectionFolders(); } bool SqlCollectionLocation::isWritable() const { // TODO: This function is also called when removing files to check // if the tracks can be removed. In such a case we should not check the space // The collection is writable if there exists a path that has more than // 500 MB free space. bool path_exists_with_space = false; bool path_exists_writable = false; QStringList folders = actualLocation(); foreach( const QString &path, folders ) { float used = KDiskFreeSpaceInfo::freeSpaceInfo( path ).used(); float total = KDiskFreeSpaceInfo::freeSpaceInfo( path ).size(); if( total <= 0 ) // protect against div by zero continue; //How did this happen? float free_space = total - used; if( free_space >= 500*1000*1000 ) // ~500 megabytes path_exists_with_space = true; QFileInfo info( path ); if( info.isWritable() ) path_exists_writable = true; } return path_exists_with_space && path_exists_writable; } bool SqlCollectionLocation::isOrganizable() const { return true; } bool SqlCollectionLocation::remove( const Meta::TrackPtr &track ) { DEBUG_BLOCK Q_ASSERT( track ); if( track->inCollection() && track->collection()->collectionId() == m_collection->collectionId() ) { bool removed; - KUrl src = track->playableUrl(); + QUrl src = track->playableUrl(); if( isGoingToRemoveSources() ) // is organize operation? { SqlCollectionLocation* destinationloc = qobject_cast( destination() ); if( destinationloc ) { src = destinationloc->m_originalUrls[track]; if( src == track->playableUrl() ) return false; } } // we are going to delete it from the database only if is no longer on disk removed = !QFile::exists( src.path() ); if( removed ) static_cast(const_cast(track.data()))->remove(); return removed; } else { debug() << "Remove Failed"; return false; } } bool SqlCollectionLocation::insert( const Meta::TrackPtr &track, const QString &url ) { if( !QFile::exists( url ) ) { warning() << Q_FUNC_INFO << "file" << url << "does not exist, not inserting into db"; return false; } // -- the target url SqlRegistry *registry = m_collection->registry(); int deviceId = m_collection->mountPointManager()->getIdForUrl( url ); QString rpath = m_collection->mountPointManager()->getRelativePath( deviceId, url ); int directoryId = registry->getDirectory( QFileInfo( url ).path() ); // -- the track uid (we can't use the original one from the old collection) Meta::FieldHash fileTags = Meta::Tag::readTags( url ); QString uid = fileTags.value( Meta::valUniqueId ).toString(); uid = m_collection->generateUidUrl( uid ); // add the right prefix // -- the track from the registry Meta::SqlTrackPtr metaTrack; metaTrack = Meta::SqlTrackPtr::staticCast( registry->getTrackFromUid( uid ) ); if( metaTrack ) { warning() << "Location is inserting a file with the same uid as an already existing one."; metaTrack->setUrl( deviceId, rpath, directoryId ); } else metaTrack = Meta::SqlTrackPtr::staticCast( registry->getTrack( deviceId, rpath, directoryId, uid ) ); Meta::ConstStatisticsPtr origStats = track->statistics(); // -- set the values metaTrack->setWriteFile( false ); // no need to write the tags back metaTrack->beginUpdate(); if( !track->name().isEmpty() ) metaTrack->setTitle( track->name() ); if( track->album() ) metaTrack->setAlbum( track->album()->name() ); if( track->artist() ) metaTrack->setArtist( track->artist()->name() ); if( track->composer() ) metaTrack->setComposer( track->composer()->name() ); if( track->year() && track->year()->year() > 0 ) metaTrack->setYear( track->year()->year() ); if( track->genre() ) metaTrack->setGenre( track->genre()->name() ); if( track->bpm() > 0 ) metaTrack->setBpm( track->bpm() ); if( !track->comment().isEmpty() ) metaTrack->setComment( track->comment() ); if( origStats->score() > 0 ) metaTrack->setScore( origStats->score() ); if( origStats->rating() > 0 ) metaTrack->setRating( origStats->rating() ); /* These tags change when transcoding. Prefer to read those from file */ if( fileTags.value( Meta::valLength, 0 ).toLongLong() > 0 ) metaTrack->setLength( fileTags.value( Meta::valLength ).value() ); else if( track->length() > 0 ) metaTrack->setLength( track->length() ); // the filesize is updated every time after the // file is changed. Doesn't make sense to set it. if( fileTags.value( Meta::valSamplerate, 0 ).toInt() > 0 ) metaTrack->setSampleRate( fileTags.value( Meta::valSamplerate ).toInt() ); else if( track->sampleRate() > 0 ) metaTrack->setSampleRate( track->sampleRate() ); if( fileTags.value( Meta::valBitrate, 0 ).toInt() > 0 ) metaTrack->setBitrate( fileTags.value( Meta::valBitrate ).toInt() ); else if( track->bitrate() > 0 ) metaTrack->setBitrate( track->bitrate() ); // createDate is already set in Track constructor if( track->modifyDate().isValid() ) metaTrack->setModifyDate( track->modifyDate() ); if( track->trackNumber() > 0 ) metaTrack->setTrackNumber( track->trackNumber() ); if( track->discNumber() > 0 ) metaTrack->setDiscNumber( track->discNumber() ); if( origStats->lastPlayed().isValid() ) metaTrack->setLastPlayed( origStats->lastPlayed() ); if( origStats->firstPlayed().isValid() ) metaTrack->setFirstPlayed( origStats->firstPlayed() ); if( origStats->playCount() > 0 ) metaTrack->setPlayCount( origStats->playCount() ); Meta::ReplayGainTag modes[] = { Meta::ReplayGain_Track_Gain, Meta::ReplayGain_Track_Peak, Meta::ReplayGain_Album_Gain, Meta::ReplayGain_Album_Peak }; for( int i=0; i<4; i++ ) if( track->replayGain( modes[i] ) != 0 ) metaTrack->setReplayGain( modes[i], track->replayGain( modes[i] ) ); Meta::LabelList labels = track->labels(); foreach( Meta::LabelPtr label, labels ) metaTrack->addLabel( label ); if( fileTags.value( Meta::valFormat, int(Amarok::Unknown) ).toInt() != int(Amarok::Unknown) ) metaTrack->setType( Amarok::FileType( fileTags.value( Meta::valFormat ).toInt() ) ); else if( Amarok::FileTypeSupport::fileType( track->type() ) != Amarok::Unknown ) metaTrack->setType( Amarok::FileTypeSupport::fileType( track->type() ) ); // Used to be updated after changes commit to prevent crash on NULL pointer access // if metaTrack had no album. if( track->album() && metaTrack->album() ) { if( track->album()->hasAlbumArtist() && !metaTrack->album()->hasAlbumArtist() ) metaTrack->setAlbumArtist( track->album()->albumArtist()->name() ); if( track->album()->hasImage() && !metaTrack->album()->hasImage() ) metaTrack->album()->setImage( track->album()->image() ); } metaTrack->endUpdate(); metaTrack->setWriteFile( true ); // we have a first shot at the meta data (expecially ratings and playcounts from media // collections) but we still need to trigger the collection scanner // to get the album and other meta data correct. // TODO m_collection->directoryWatcher()->delayedIncrementalScan( QFileInfo(url).path() ); return true; } void SqlCollectionLocation::showDestinationDialog( const Meta::TrackList &tracks, bool removeSources, const Transcoding::Configuration &configuration ) { DEBUG_BLOCK setGoingToRemoveSources( removeSources ); KIO::filesize_t transferSize = 0; foreach( Meta::TrackPtr track, tracks ) transferSize += track->filesize(); QStringList actual_folders = actualLocation(); // the folders in the collection QStringList available_folders; // the folders which have freespace available foreach(QString path, actual_folders) { if( path.isEmpty() ) continue; debug() << "Path" << path; KDiskFreeSpaceInfo spaceInfo = KDiskFreeSpaceInfo::freeSpaceInfo( path ); if( !spaceInfo.isValid() ) continue; KIO::filesize_t totalCapacity = spaceInfo.size(); KIO::filesize_t used = spaceInfo.used(); KIO::filesize_t freeSpace = totalCapacity - used; debug() << "used:" << used; debug() << "total:" << totalCapacity; debug() << "Free space" << freeSpace; debug() << "transfersize" << transferSize; if( totalCapacity <= 0 ) // protect against div by zero continue; //How did this happen? QFileInfo info( path ); // since bad things happen when drives become totally full // we make sure there is at least ~500MB left // finally, ensure the path is writable debug() << ( freeSpace - transferSize ); if( ( freeSpace - transferSize ) > 1024*1024*500 && info.isWritable() ) available_folders << path; } if( available_folders.size() <= 0 ) { debug() << "No space available or not writable"; CollectionLocationDelegate *delegate = Amarok::Components::collectionLocationDelegate(); delegate->notWriteable( this ); abort(); return; } OrganizeCollectionDelegate *delegate = m_delegateFactory->createDelegate(); delegate->setTracks( tracks ); delegate->setFolders( available_folders ); delegate->setIsOrganizing( ( collection() == source()->collection() ) ); delegate->setTranscodingConfiguration( configuration ); delegate->setCaption( operationText( configuration ) ); connect( delegate, SIGNAL(accepted()), SLOT(slotDialogAccepted()) ); connect( delegate, SIGNAL(rejected()), SLOT(slotDialogRejected()) ); delegate->show(); } void SqlCollectionLocation::slotDialogAccepted() { DEBUG_BLOCK sender()->deleteLater(); OrganizeCollectionDelegate *ocDelegate = qobject_cast( sender() ); m_destinations = ocDelegate->destinations(); m_overwriteFiles = ocDelegate->overwriteDestinations(); if( isGoingToRemoveSources() ) { CollectionLocationDelegate *delegate = Amarok::Components::collectionLocationDelegate(); const bool del = delegate->reallyMove( this, m_destinations.keys() ); if( !del ) { abort(); return; } } slotShowDestinationDialogDone(); } void SqlCollectionLocation::slotDialogRejected() { DEBUG_BLOCK sender()->deleteLater(); abort(); } void SqlCollectionLocation::slotJobFinished( KJob *job ) { DEBUG_BLOCK Meta::TrackPtr track = m_jobs.value( job ); if( job->error() && job->error() != KIO::ERR_FILE_ALREADY_EXIST ) { //TODO: proper error handling warning() << "An error occurred when copying a file: " << job->errorString(); source()->transferError( track, KIO::buildErrorString( job->error(), job->errorString() ) ); m_destinations.remove( track ); } else source()->transferSuccessful( track ); m_jobs.remove( job ); job->deleteLater(); } void SqlCollectionLocation::slotRemoveJobFinished( KJob *job ) { DEBUG_BLOCK Meta::TrackPtr track = m_removejobs.value( job ); if( job->error() ) { //TODO: proper error handling warning() << "An error occurred when removing a file: " << job->errorString(); } // -- remove the track from the database if it's gone if( !QFile(track->playableUrl().path()).exists() ) { // Remove the track from the database remove( track ); //we assume that KIO works correctly... transferSuccessful( track ); } else { transferError( track, KIO::buildErrorString( job->error(), job->errorString() ) ); } m_removejobs.remove( job ); job->deleteLater(); if( !startNextRemoveJob() ) { slotRemoveOperationFinished(); } } void SqlCollectionLocation::slotTransferJobFinished( KJob* job ) { DEBUG_BLOCK if( job->error() ) { debug() << job->errorText(); } // filter the list of destinations to only include tracks // that were successfully copied foreach( const Meta::TrackPtr &track, m_destinations.keys() ) { if( QFile::exists( m_destinations[ track ] ) ) insert( track, m_destinations[ track ] ); m_originalUrls[track] = track->playableUrl(); } debug () << "m_originalUrls" << m_originalUrls; slotCopyOperationFinished(); } void SqlCollectionLocation::slotTransferJobAborted() { DEBUG_BLOCK if( !m_transferjob ) return; m_transferjob->kill(); // filter the list of destinations to only include tracks // that were successfully copied foreach( const Meta::TrackPtr &track, m_destinations.keys() ) { if( QFile::exists( m_destinations[ track ] ) ) insert( track, m_destinations[ track ] ); // was already copied, so have to insert it in the db m_originalUrls[track] = track->playableUrl(); } abort(); } void -SqlCollectionLocation::copyUrlsToCollection( const QMap &sources, +SqlCollectionLocation::copyUrlsToCollection( const QMap &sources, const Transcoding::Configuration &configuration ) { DEBUG_BLOCK m_sources = sources; QString statusBarTxt = operationInProgressText( configuration, sources.count() ); m_transferjob = new TransferJob( this, configuration ); Amarok::Components::logger()->newProgressOperation( m_transferjob, statusBarTxt, this, SLOT(slotTransferJobAborted()) ); connect( m_transferjob, SIGNAL(result(KJob*)), SLOT(slotTransferJobFinished(KJob*)) ); m_transferjob->start(); } void SqlCollectionLocation::removeUrlsFromCollection( const Meta::TrackList &sources ) { DEBUG_BLOCK m_removetracks = sources; if( !startNextRemoveJob() ) //this signal needs to be called no matter what, even if there are no job finishes to call it slotRemoveOperationFinished(); } void SqlCollectionLocation::setOrganizeCollectionDelegateFactory( OrganizeCollectionDelegateFactory *fac ) { m_delegateFactory = fac; } bool SqlCollectionLocation::startNextJob( const Transcoding::Configuration configuration ) { DEBUG_BLOCK if( !m_sources.isEmpty() ) { Meta::TrackPtr track = m_sources.keys().first(); - KUrl src = m_sources.take( track ); + QUrl src = m_sources.take( track ); - KUrl dest = m_destinations[ track ]; + QUrl dest = m_destinations[ track ]; dest.cleanPath(); src.cleanPath(); bool hasMoodFile = QFile::exists( moodFile( src ).toLocalFile() ); bool isJustCopy = configuration.isJustCopy( track ); if( isJustCopy ) debug() << "copying from " << src << " to " << dest; else debug() << "transcoding from " << src << " to " << dest; KFileItem srcInfo( src, src.toMimeDataString(), KFileItem::Unknown ); if( !srcInfo.isFile() ) { warning() << "Source track" << src << "was no file"; source()->transferError( track, i18n( "Source track does not exist: %1", src.pathOrUrl() ) ); return true; // Attempt to copy/move the next item in m_sources } QFileInfo destInfo( dest.pathOrUrl() ); QDir dir = destInfo.dir(); if( !dir.exists() ) { if( !dir.mkpath( "." ) ) { warning() << "Could not create directory " << dir; source()->transferError(track, i18n( "Could not create directory: %1", dir.path() ) ); return true; // Attempt to copy/move the next item in m_sources } } KIO::JobFlags flags; if( isJustCopy ) { flags = KIO::HideProgressInfo; if( m_overwriteFiles ) { flags |= KIO::Overwrite; } } KJob *job = 0; KJob *moodJob = 0; if( src.equals( dest ) ) { warning() << "move to itself found: " << destInfo.absoluteFilePath(); m_transferjob->slotJobFinished( 0 ); if( m_sources.isEmpty() ) return false; return true; } else if( isGoingToRemoveSources() && source()->collection() == collection() ) { debug() << "moving!"; job = KIO::file_move( src, dest, -1, flags ); if( hasMoodFile ) { - KUrl moodSrc = moodFile( src ); - KUrl moodDest = moodFile( dest ); + QUrl moodSrc = moodFile( src ); + QUrl moodDest = moodFile( dest ); moodJob = KIO::file_move( moodSrc, moodDest, -1, flags ); } } else { //later on in the case that remove is called, the file will be deleted because we didn't apply moveByDestination to the track if( isJustCopy ) job = KIO::file_copy( src, dest, -1, flags ); else { QString destPath = dest.path(); destPath.truncate( dest.path().lastIndexOf( '.' ) + 1 ); destPath.append( Amarok::Components::transcodingController()-> format( configuration.encoder() )->fileExtension() ); dest.setPath( destPath ); job = new Transcoding::Job( src, dest, configuration, this ); job->start(); } if( hasMoodFile ) { - KUrl moodSrc = moodFile( src ); - KUrl moodDest = moodFile( dest ); + QUrl moodSrc = moodFile( src ); + QUrl moodDest = moodFile( dest ); moodJob = KIO::file_copy( moodSrc, moodDest, -1, flags ); } } if( job ) //just to be safe { connect( job, SIGNAL(result(KJob*)), SLOT(slotJobFinished(KJob*)) ); connect( job, SIGNAL(result(KJob*)), m_transferjob, SLOT(slotJobFinished(KJob*)) ); m_transferjob->addSubjob( job ); if( moodJob ) { connect( moodJob, SIGNAL(result(KJob*)), m_transferjob, SLOT(slotJobFinished(KJob*)) ); m_transferjob->addSubjob( moodJob ); } QString name = track->prettyName(); if( track->artist() ) name = QString( "%1 - %2" ).arg( track->artist()->name(), track->prettyName() ); if( isJustCopy ) m_transferjob->emitInfo( i18n( "Transferring: %1", name ) ); else m_transferjob->emitInfo( i18n( "Transcoding: %1", name ) ); m_jobs.insert( job, track ); return true; } debug() << "JOB NULL OMG!11"; } return false; } bool SqlCollectionLocation::startNextRemoveJob() { DEBUG_BLOCK while ( !m_removetracks.isEmpty() ) { Meta::TrackPtr track = m_removetracks.takeFirst(); - // KUrl src = track->playableUrl(); - KUrl src = track->playableUrl(); - KUrl srcMoodFile = moodFile( src ); + // QUrl src = track->playableUrl(); + QUrl src = track->playableUrl(); + QUrl srcMoodFile = moodFile( src ); debug() << "isGoingToRemoveSources() " << isGoingToRemoveSources(); if( isGoingToRemoveSources() && destination() ) // is organize operation? { SqlCollectionLocation* destinationloc = dynamic_cast( destination() ); // src = destinationloc->m_originalUrls[track]; if( destinationloc && src == destinationloc->m_destinations[track] ) { debug() << "src == dst ("<prettyName(); if( track->artist() ) name = QString( "%1 - %2" ).arg( track->artist()->name(), track->prettyName() ); Amarok::Components::logger()->newProgressOperation( job, i18n( "Removing: %1", name ) ); m_removejobs.insert( job, track ); return true; } break; } return false; } -KUrl -SqlCollectionLocation::moodFile( const KUrl &track ) const +QUrl +SqlCollectionLocation::moodFile( const QUrl &track ) const { - KUrl moodPath = track; - moodPath.setFileName( '.' + moodPath.fileName().replace( QRegExp( "(\\.\\w{2,5})$" ), ".mood" ) ); + QUrl moodPath = track; + moodPath = moodPath.adjusted(QUrl::RemoveFilename); + moodPath.setPath(moodPath.path() + '.' + moodPath.fileName().replace( QRegExp( "(\\.\\w{2,5})$" ), ".mood" ) ); return moodPath; } TransferJob::TransferJob( SqlCollectionLocation * location, const Transcoding::Configuration & configuration ) : KCompositeJob( 0 ) , m_location( location ) , m_killed( false ) , m_transcodeFormat( configuration ) { setCapabilities( KJob::Killable ); debug() << "TransferJob::TransferJob"; } bool TransferJob::addSubjob( KJob* job ) { connect( job, SIGNAL(processedAmount(KJob*,KJob::Unit,qulonglong)), this, SLOT(propagateProcessedAmount(KJob*,KJob::Unit,qulonglong)) ); //KCompositeJob::addSubjob doesn't handle progress reporting. return KCompositeJob::addSubjob( job ); } void TransferJob::emitInfo(const QString& message) { emit infoMessage( this, message ); } void TransferJob::slotResult( KJob *job ) { // When copying without overwriting some files might already be // there and it is not a reason for stopping entire transfer. if ( job->error() == KIO::ERR_FILE_ALREADY_EXIST ) removeSubjob( job ); else KCompositeJob::slotResult( job ); } void TransferJob::start() { DEBUG_BLOCK if( m_location == 0 ) { setError( 1 ); setErrorText( "Location is null!" ); emitResult(); return; } QTimer::singleShot( 0, this, SLOT(doWork()) ); } void TransferJob::doWork() { DEBUG_BLOCK setTotalAmount( KJob::Files, m_location->m_sources.size() ); setTotalAmount( KJob::Bytes, m_location->m_sources.size() * 1000 ); setProcessedAmount( KJob::Files, 0 ); if( !m_location->startNextJob( m_transcodeFormat ) ) { if( !hasSubjobs() ) emitResult(); } } void TransferJob::slotJobFinished( KJob* job ) { DEBUG_BLOCK if( job ) removeSubjob( job ); if( m_killed ) { debug() << "slotJobFinished entered, but it should be killed!"; return; } setProcessedAmount( KJob::Files, processedAmount( KJob::Files ) + 1 ); emitPercent( processedAmount( KJob::Files ) * 1000, totalAmount( KJob::Bytes ) ); debug() << "processed" << processedAmount( KJob::Files ) << " totalAmount" << totalAmount( KJob::Files ); if( !m_location->startNextJob( m_transcodeFormat ) ) { debug() << "sources empty"; // don't quit if there are still subjobs if( !hasSubjobs() ) emitResult(); else debug() << "have subjobs"; } } bool TransferJob::doKill() { DEBUG_BLOCK m_killed = true; foreach( KJob* job, subjobs() ) { job->kill(); } clearSubjobs(); return KJob::doKill(); } void TransferJob::propagateProcessedAmount( KJob *job, KJob::Unit unit, qulonglong amount ) //SLOT { if( unit == KJob::Bytes ) { qulonglong currentJobAmount = ( static_cast< qreal >( amount ) / job->totalAmount( KJob::Bytes ) ) * 1000; setProcessedAmount( KJob::Bytes, processedAmount( KJob::Files ) * 1000 + currentJobAmount ); emitPercent( processedAmount( KJob::Bytes ), totalAmount( KJob::Bytes ) ); } } diff --git a/src/core-impl/collections/db/sql/SqlCollectionLocation.h b/src/core-impl/collections/db/sql/SqlCollectionLocation.h index da1b2e57ed..89e7857c98 100644 --- a/src/core-impl/collections/db/sql/SqlCollectionLocation.h +++ b/src/core-impl/collections/db/sql/SqlCollectionLocation.h @@ -1,165 +1,165 @@ /**************************************************************************************** * Copyright (c) 2007 Maximilian Kossick * * Copyright (c) 2008 Jason A. Donenfeld * * Copyright (c) 2010 Casey Link * * Copyright (c) 2010 Teo Mrnjavac * * * * 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, see . * ****************************************************************************************/ #ifndef AMAROK_SQLCOLLECTIONLOCATION_H #define AMAROK_SQLCOLLECTIONLOCATION_H #include "amarok_sqlcollection_export.h" #include "core/collections/CollectionLocation.h" #include #include #include #include #include class OrganizeCollectionDelegateFactory; namespace Collections { class SqlCollection; class SqlCollectionLocation; /** * @class TransferJob * A simple class that provides KJob functionality (progress reporting, aborting, etc) for sqlcollectionlocation. * It calls SqlCollectionLocation::startNextJob() */ class TransferJob : public KCompositeJob { Q_OBJECT public: TransferJob( SqlCollectionLocation * location, const Transcoding::Configuration & configuration ); void start(); virtual bool addSubjob( KJob* job ); void emitInfo( const QString &message ); public slots: /** * A move or copy job finished */ void slotJobFinished( KJob *job ); protected slots: void slotResult( KJob *job ); void doWork(); void propagateProcessedAmount( KJob *job, KJob::Unit unit, qulonglong amount); protected: virtual bool doKill(); private: SqlCollectionLocation* m_location; bool m_killed; Transcoding::Configuration m_transcodeFormat; }; class AMAROK_SQLCOLLECTION_EXPORT SqlCollectionLocation : public CollectionLocation { Q_OBJECT public: SqlCollectionLocation( SqlCollection *collection ); virtual ~SqlCollectionLocation(); virtual QString prettyLocation() const; virtual QStringList actualLocation() const; virtual bool isWritable() const; virtual bool isOrganizable() const; bool remove( const Meta::TrackPtr &track ); virtual bool insert( const Meta::TrackPtr &track, const QString &url ); //dependency injectors void setOrganizeCollectionDelegateFactory( OrganizeCollectionDelegateFactory *fac ); protected: virtual void showDestinationDialog( const Meta::TrackList &tracks, bool removeSources, const Transcoding::Configuration &configuration ); - virtual void copyUrlsToCollection( const QMap &sources, + virtual void copyUrlsToCollection( const QMap &sources, const Transcoding::Configuration & configuration ); virtual void removeUrlsFromCollection( const Meta::TrackList &sources ); private slots: void slotDialogAccepted(); void slotDialogRejected(); void slotJobFinished( KJob *job ); void slotRemoveJobFinished( KJob *job ); void slotTransferJobFinished( KJob *job ); void slotTransferJobAborted(); private: - KUrl moodFile( const KUrl &track ) const; + QUrl moodFile( const QUrl &track ) const; void migrateLabels( const QMap &trackMap ); bool startNextJob( const Transcoding::Configuration configuration ); bool startNextRemoveJob(); Collections::SqlCollection *m_collection; OrganizeCollectionDelegateFactory *m_delegateFactory; QMap m_destinations; - QMap m_sources; + QMap m_sources; Meta::TrackList m_removetracks; - QHash m_originalUrls; + QHash m_originalUrls; bool m_overwriteFiles; QMap m_jobs; QMap m_removejobs; TransferJob* m_transferjob; friend class TransferJob; // so the transfer job can run the jobs }; class SqlCollectionLocationFactory { public: virtual SqlCollectionLocation* createSqlCollectionLocation() const = 0; virtual ~SqlCollectionLocationFactory() {} }; } //namespace Collections class AMAROK_SQLCOLLECTION_EXPORT OrganizeCollectionDelegate : public QObject { Q_OBJECT public: OrganizeCollectionDelegate() : QObject() {} virtual ~ OrganizeCollectionDelegate() {} virtual void setTracks( const Meta::TrackList &tracks ) = 0; virtual void setFolders( const QStringList &folders ) = 0; virtual void setIsOrganizing( bool organizing ) = 0; virtual void setTranscodingConfiguration( const Transcoding::Configuration &configuration ) = 0; virtual void setCaption( const QString &caption ) = 0; virtual void show() = 0; virtual bool overwriteDestinations() const = 0; virtual QMap destinations() const = 0; signals: void accepted(); void rejected(); }; class OrganizeCollectionDelegateFactory { public: virtual OrganizeCollectionDelegate* createDelegate() = 0; virtual ~OrganizeCollectionDelegateFactory() {} }; #endif diff --git a/src/core-impl/collections/db/sql/SqlMeta.cpp b/src/core-impl/collections/db/sql/SqlMeta.cpp index 3968cde105..553533a4c1 100644 --- a/src/core-impl/collections/db/sql/SqlMeta.cpp +++ b/src/core-impl/collections/db/sql/SqlMeta.cpp @@ -1,2243 +1,2243 @@ /**************************************************************************************** * Copyright (c) 2007 Maximilian Kossick * * Copyright (c) 2008 Daniel Winter * * Copyright (c) 2010 Ralf Engels * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "SqlMeta" #include "SqlMeta.h" #include "amarokconfig.h" #include "SqlCapabilities.h" #include "SqlCollection.h" #include "SqlQueryMaker.h" #include "SqlRegistry.h" #include "SqlReadLabelCapability.h" #include "SqlWriteLabelCapability.h" #include "MetaTagLib.h" // for getting an embedded cover #include "amarokurls/BookmarkMetaActions.h" #include #include "core/meta/support/MetaUtility.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include "core/capabilities/BookmarkThisCapability.h" #include "core-impl/capabilities/AlbumActionsCapability.h" #include "core-impl/collections/db/MountPointManager.h" #include "core-impl/collections/support/ArtistHelper.h" #include "core-impl/collections/support/jobs/WriteTagsJob.h" #include "covermanager/CoverCache.h" #include "covermanager/CoverFetcher.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include // additional constants namespace Meta { static const qint64 valAlbumId = valCustom + 1; } using namespace Meta; QString SqlTrack::getTrackReturnValues() { //do not use any weird column names that contains commas: this will break getTrackReturnValuesCount() // NOTE: when changing this, always check that SqlTrack::TrackReturnIndex enum remains valid return "urls.id, urls.deviceid, urls.rpath, urls.directory, urls.uniqueid, " "tracks.id, tracks.title, tracks.comment, " "tracks.tracknumber, tracks.discnumber, " "statistics.score, statistics.rating, " "tracks.bitrate, tracks.length, " "tracks.filesize, tracks.samplerate, " "statistics.id, " "statistics.createdate, statistics.accessdate, " "statistics.playcount, tracks.filetype, tracks.bpm, " "tracks.createdate, tracks.modifydate, tracks.albumgain, tracks.albumpeakgain, " "tracks.trackgain, tracks.trackpeakgain, " "artists.name, artists.id, " // TODO: just reading the id should be sufficient "albums.name, albums.id, albums.artist, " // TODO: again here "genres.name, genres.id, " // TODO: again here "composers.name, composers.id, " // TODO: again here "years.name, years.id"; // TODO: again here } QString SqlTrack::getTrackJoinConditions() { return "LEFT JOIN tracks ON urls.id = tracks.url " "LEFT JOIN statistics ON urls.id = statistics.url " "LEFT JOIN artists ON tracks.artist = artists.id " "LEFT JOIN albums ON tracks.album = albums.id " "LEFT JOIN genres ON tracks.genre = genres.id " "LEFT JOIN composers ON tracks.composer = composers.id " "LEFT JOIN years ON tracks.year = years.id"; } int SqlTrack::getTrackReturnValueCount() { static int count = getTrackReturnValues().split( ',' ).count(); return count; } SqlTrack::SqlTrack( Collections::SqlCollection *collection, int deviceId, const QString &rpath, int directoryId, const QString uidUrl ) : Track() , m_collection( collection ) , m_batchUpdate( 0 ) , m_writeFile( true ) , m_labelsInCache( false ) { m_batchUpdate = 1; // I don't want commits yet m_urlId = -1; // this will be set with the first database write m_trackId = -1; // this will be set with the first database write m_statisticsId = -1; setUrl( deviceId, rpath, directoryId ); m_url = m_cache.value( Meta::valUrl ).toString(); // SqlRegistry already has this url setUidUrl( uidUrl ); m_uid = m_cache.value( Meta::valUniqueId ).toString(); // SqlRegistry already has this uid // ensure that these values get a correct database id m_cache.insert( Meta::valAlbum, QVariant() ); m_cache.insert( Meta::valArtist, QVariant() ); m_cache.insert( Meta::valComposer, QVariant() ); m_cache.insert( Meta::valYear, QVariant() ); m_cache.insert( Meta::valGenre, QVariant() ); m_trackNumber = 0; m_discNumber = 0; m_score = 0; m_rating = 0; m_bitrate = 0; m_length = 0; m_filesize = 0; m_sampleRate = 0; m_playCount = 0; m_bpm = 0.0; m_createDate = QDateTime::currentDateTime(); m_cache.insert( Meta::valCreateDate, m_createDate ); // ensure that the created date is written the next time m_trackGain = 0.0; m_trackPeakGain = 0.0; m_albumGain = 0.0; m_albumPeakGain = 0.0; m_batchUpdate = 0; // reset in-batch-update without committing m_filetype = Amarok::Unknown; } SqlTrack::SqlTrack( Collections::SqlCollection *collection, const QStringList &result ) : Track() , m_collection( collection ) , m_batchUpdate( 0 ) , m_writeFile( true ) , m_labelsInCache( false ) { QStringList::ConstIterator iter = result.constBegin(); m_urlId = (*(iter++)).toInt(); Q_ASSERT( m_urlId > 0 && "refusing to create SqlTrack with non-positive urlId, please file a bug" ); m_deviceId = (*(iter++)).toInt(); Q_ASSERT( m_deviceId != 0 && "refusing to create SqlTrack with zero deviceId, please file a bug" ); m_rpath = *(iter++); m_directoryId = (*(iter++)).toInt(); Q_ASSERT( m_directoryId > 0 && "refusing to create SqlTrack with non-positive directoryId, please file a bug" ); - m_url = KUrl( m_collection->mountPointManager()->getAbsolutePath( m_deviceId, m_rpath ) ); + m_url = QUrl( m_collection->mountPointManager()->getAbsolutePath( m_deviceId, m_rpath ) ); m_uid = *(iter++); m_trackId = (*(iter++)).toInt(); m_title = *(iter++); m_comment = *(iter++); m_trackNumber = (*(iter++)).toInt(); m_discNumber = (*(iter++)).toInt(); m_score = (*(iter++)).toDouble(); m_rating = (*(iter++)).toInt(); m_bitrate = (*(iter++)).toInt(); m_length = (*(iter++)).toInt(); m_filesize = (*(iter++)).toInt(); m_sampleRate = (*(iter++)).toInt(); m_statisticsId = (*(iter++)).toInt(); uint time = (*(iter++)).toUInt(); if( time > 0 ) m_firstPlayed = QDateTime::fromTime_t(time); time = (*(iter++)).toUInt(); if( time > 0 ) m_lastPlayed = QDateTime::fromTime_t(time); m_playCount = (*(iter++)).toInt(); m_filetype = Amarok::FileType( (*(iter++)).toInt() ); m_bpm = (*(iter++)).toFloat(); m_createDate = QDateTime::fromTime_t((*(iter++)).toUInt()); m_modifyDate = QDateTime::fromTime_t((*(iter++)).toUInt()); // if there is no track gain, we assume a gain of 0 // if there is no album gain, we use the track gain QString albumGain = *(iter++); QString albumPeakGain = *(iter++); m_trackGain = (*(iter++)).toDouble(); m_trackPeakGain = (*(iter++)).toDouble(); if ( albumGain.isEmpty() ) { m_albumGain = m_trackGain; m_albumPeakGain = m_trackPeakGain; } else { m_albumGain = albumGain.toDouble(); m_albumPeakGain = albumPeakGain.toDouble(); } SqlRegistry* registry = m_collection->registry(); QString artist = *(iter++); int artistId = (*(iter++)).toInt(); if( artistId > 0 ) m_artist = registry->getArtist( artistId, artist ); QString album = *(iter++); int albumId =(*(iter++)).toInt(); int albumArtistId = (*(iter++)).toInt(); if( albumId > 0 ) // sanity check m_album = registry->getAlbum( albumId, album, albumArtistId ); QString genre = *(iter++); int genreId = (*(iter++)).toInt(); if( genreId > 0 ) // sanity check m_genre = registry->getGenre( genreId, genre ); QString composer = *(iter++); int composerId = (*(iter++)).toInt(); if( composerId > 0 ) // sanity check m_composer = registry->getComposer( composerId, composer ); QString year = *(iter++); int yearId = (*(iter++)).toInt(); if( yearId > 0 ) // sanity check m_year = registry->getYear( year.toInt(), yearId ); //Q_ASSERT_X( iter == result.constEnd(), "SqlTrack( Collections::SqlCollection*, QStringList )", "number of expected fields did not match number of actual fields: expected " + result.size() ); } SqlTrack::~SqlTrack() { QWriteLocker locker( &m_lock ); if( !m_cache.isEmpty() ) warning() << "Destroying track with unwritten meta information." << m_title << "cache:" << m_cache; if( m_batchUpdate ) warning() << "Destroying track with unclosed batch update." << m_title; } QString SqlTrack::name() const { QReadLocker locker( &m_lock ); return m_title; } QString SqlTrack::prettyName() const { if ( !name().isEmpty() ) return name(); return prettyTitle( m_url.fileName() ); } void SqlTrack::setTitle( const QString &newTitle ) { QWriteLocker locker( &m_lock ); if ( m_title != newTitle ) commitIfInNonBatchUpdate( Meta::valTitle, newTitle ); } -KUrl +QUrl SqlTrack::playableUrl() const { QReadLocker locker( &m_lock ); return m_url; } QString SqlTrack::prettyUrl() const { QReadLocker locker( &m_lock ); return m_url.path(); } void SqlTrack::setUrl( int deviceId, const QString &rpath, int directoryId ) { QWriteLocker locker( &m_lock ); if( m_deviceId == deviceId && m_rpath == rpath && m_directoryId == directoryId ) return; m_deviceId = deviceId; m_rpath = rpath; m_directoryId = directoryId; commitIfInNonBatchUpdate( Meta::valUrl, m_collection->mountPointManager()->getAbsolutePath( m_deviceId, m_rpath ) ); } QString SqlTrack::uidUrl() const { QReadLocker locker( &m_lock ); return m_uid; } void SqlTrack::setUidUrl( const QString &uid ) { QWriteLocker locker( &m_lock ); // -- ensure that the uid starts with the collections protocol (amarok-sqltrackuid) QString newid = uid; QString protocol; if( m_collection ) protocol = m_collection->uidUrlProtocol()+"://"; if( !newid.startsWith( protocol ) ) newid.prepend( protocol ); m_cache.insert( Meta::valUniqueId, newid ); if( m_batchUpdate == 0 ) { debug() << "setting uidUrl manually...did you really mean to do this?"; commitIfInNonBatchUpdate(); } } QString SqlTrack::notPlayableReason() const { return localFileNotPlayableReason( playableUrl().toLocalFile() ); } bool SqlTrack::isEditable() const { QReadLocker locker( &m_lock ); QFile::Permissions p = QFile::permissions( m_url.path() ); const bool editable = ( p & QFile::WriteUser ) || ( p & QFile::WriteGroup ) || ( p & QFile::WriteOther ); return m_collection && QFile::exists( m_url.path() ) && editable; } Meta::AlbumPtr SqlTrack::album() const { QReadLocker locker( &m_lock ); return m_album; } void SqlTrack::setAlbum( const QString &newAlbum ) { QWriteLocker locker( &m_lock ); if( !m_album || m_album->name() != newAlbum ) commitIfInNonBatchUpdate( Meta::valAlbum, newAlbum ); } void SqlTrack::setAlbum( int albumId ) { QWriteLocker locker( &m_lock ); commitIfInNonBatchUpdate( Meta::valAlbumId, albumId ); } Meta::ArtistPtr SqlTrack::artist() const { QReadLocker locker( &m_lock ); return m_artist; } void SqlTrack::setArtist( const QString &newArtist ) { QWriteLocker locker( &m_lock ); if( !m_artist || m_artist->name() != newArtist ) commitIfInNonBatchUpdate( Meta::valArtist, newArtist ); } void SqlTrack::setAlbumArtist( const QString &newAlbumArtist ) { if( m_album.isNull() ) return; if( !newAlbumArtist.compare( "Various Artists", Qt::CaseInsensitive ) || !newAlbumArtist.compare( i18n( "Various Artists" ), Qt::CaseInsensitive ) ) { commitIfInNonBatchUpdate( Meta::valCompilation, true ); } else { m_cache.insert( Meta::valAlbumArtist, ArtistHelper::realTrackArtist( newAlbumArtist ) ); m_cache.insert( Meta::valCompilation, false ); commitIfInNonBatchUpdate(); } } Meta::ComposerPtr SqlTrack::composer() const { QReadLocker locker( &m_lock ); return m_composer; } void SqlTrack::setComposer( const QString &newComposer ) { QWriteLocker locker( &m_lock ); if( !m_composer || m_composer->name() != newComposer ) commitIfInNonBatchUpdate( Meta::valComposer, newComposer ); } Meta::YearPtr SqlTrack::year() const { QReadLocker locker( &m_lock ); return m_year; } void SqlTrack::setYear( int newYear ) { QWriteLocker locker( &m_lock ); if( !m_year || m_year->year() != newYear ) commitIfInNonBatchUpdate( Meta::valYear, newYear ); } Meta::GenrePtr SqlTrack::genre() const { QReadLocker locker( &m_lock ); return m_genre; } void SqlTrack::setGenre( const QString &newGenre ) { QWriteLocker locker( &m_lock ); if( !m_genre || m_genre->name() != newGenre ) commitIfInNonBatchUpdate( Meta::valGenre, newGenre ); } QString SqlTrack::type() const { QReadLocker locker( &m_lock ); return m_url.isLocalFile() ? Amarok::FileTypeSupport::toString( m_filetype ) // don't localize. This is used in different files to identify streams, see EngineController quirks : "stream"; } void SqlTrack::setType( Amarok::FileType newType ) { QWriteLocker locker( &m_lock ); if ( m_filetype != newType ) commitIfInNonBatchUpdate( Meta::valFormat, int(newType) ); } qreal SqlTrack::bpm() const { QReadLocker locker( &m_lock ); return m_bpm; } void SqlTrack::setBpm( const qreal newBpm ) { QWriteLocker locker( &m_lock ); if ( m_bpm != newBpm ) commitIfInNonBatchUpdate( Meta::valBpm, newBpm ); } QString SqlTrack::comment() const { QReadLocker locker( &m_lock ); return m_comment; } void SqlTrack::setComment( const QString &newComment ) { QWriteLocker locker( &m_lock ); if( newComment != m_comment ) commitIfInNonBatchUpdate( Meta::valComment, newComment ); } double SqlTrack::score() const { QReadLocker locker( &m_lock ); return m_score; } void SqlTrack::setScore( double newScore ) { QWriteLocker locker( &m_lock ); newScore = qBound( double(0), newScore, double(100) ); if( qAbs( newScore - m_score ) > 0.001 ) // we don't commit for minimal changes commitIfInNonBatchUpdate( Meta::valScore, newScore ); } int SqlTrack::rating() const { QReadLocker locker( &m_lock ); return m_rating; } void SqlTrack::setRating( int newRating ) { QWriteLocker locker( &m_lock ); newRating = qBound( 0, newRating, 10 ); if( newRating != m_rating ) commitIfInNonBatchUpdate( Meta::valRating, newRating ); } qint64 SqlTrack::length() const { QReadLocker locker( &m_lock ); return m_length; } void SqlTrack::setLength( qint64 newLength ) { QWriteLocker locker( &m_lock ); if( newLength != m_length ) commitIfInNonBatchUpdate( Meta::valLength, newLength ); } int SqlTrack::filesize() const { QReadLocker locker( &m_lock ); return m_filesize; } int SqlTrack::sampleRate() const { QReadLocker locker( &m_lock ); return m_sampleRate; } void SqlTrack::setSampleRate( int newSampleRate ) { QWriteLocker locker( &m_lock ); if( newSampleRate != m_sampleRate ) commitIfInNonBatchUpdate( Meta::valSamplerate, newSampleRate ); } int SqlTrack::bitrate() const { QReadLocker locker( &m_lock ); return m_bitrate; } void SqlTrack::setBitrate( int newBitrate ) { QWriteLocker locker( &m_lock ); if( newBitrate != m_bitrate ) commitIfInNonBatchUpdate( Meta::valBitrate, newBitrate ); } QDateTime SqlTrack::createDate() const { QReadLocker locker( &m_lock ); return m_createDate; } QDateTime SqlTrack::modifyDate() const { QReadLocker locker( &m_lock ); return m_modifyDate; } void SqlTrack::setModifyDate( const QDateTime &newTime ) { QWriteLocker locker( &m_lock ); if( newTime != m_modifyDate ) commitIfInNonBatchUpdate( Meta::valModified, newTime ); } int SqlTrack::trackNumber() const { QReadLocker locker( &m_lock ); return m_trackNumber; } void SqlTrack::setTrackNumber( int newTrackNumber ) { QWriteLocker locker( &m_lock ); if( newTrackNumber != m_trackNumber ) commitIfInNonBatchUpdate( Meta::valTrackNr, newTrackNumber ); } int SqlTrack::discNumber() const { QReadLocker locker( &m_lock ); return m_discNumber; } void SqlTrack::setDiscNumber( int newDiscNumber ) { QWriteLocker locker( &m_lock ); if( newDiscNumber != m_discNumber ) commitIfInNonBatchUpdate( Meta::valDiscNr, newDiscNumber ); } QDateTime SqlTrack::lastPlayed() const { QReadLocker locker( &m_lock ); return m_lastPlayed; } void SqlTrack::setLastPlayed( const QDateTime &newTime ) { QWriteLocker locker( &m_lock ); if( newTime != m_lastPlayed ) commitIfInNonBatchUpdate( Meta::valLastPlayed, newTime ); } QDateTime SqlTrack::firstPlayed() const { QReadLocker locker( &m_lock ); return m_firstPlayed; } void SqlTrack::setFirstPlayed( const QDateTime &newTime ) { QWriteLocker locker( &m_lock ); if( newTime != m_firstPlayed ) commitIfInNonBatchUpdate( Meta::valFirstPlayed, newTime ); } int SqlTrack::playCount() const { QReadLocker locker( &m_lock ); return m_playCount; } void SqlTrack::setPlayCount( const int newCount ) { QWriteLocker locker( &m_lock ); if( newCount != m_playCount ) commitIfInNonBatchUpdate( Meta::valPlaycount, newCount ); } qreal SqlTrack::replayGain( ReplayGainTag mode ) const { QReadLocker locker(&(const_cast(this)->m_lock)); switch( mode ) { case Meta::ReplayGain_Track_Gain: return m_trackGain; case Meta::ReplayGain_Track_Peak: return m_trackPeakGain; case Meta::ReplayGain_Album_Gain: return m_albumGain; case Meta::ReplayGain_Album_Peak: return m_albumPeakGain; } return 0.0; } void SqlTrack::setReplayGain( Meta::ReplayGainTag mode, qreal value ) { if( qAbs( value - replayGain( mode ) ) < 0.01 ) return; { QWriteLocker locker( &m_lock ); switch( mode ) { case Meta::ReplayGain_Track_Gain: m_cache.insert( Meta::valTrackGain, value ); break; case Meta::ReplayGain_Track_Peak: m_cache.insert( Meta::valTrackGainPeak, value ); break; case Meta::ReplayGain_Album_Gain: m_cache.insert( Meta::valAlbumGain, value ); break; case Meta::ReplayGain_Album_Peak: m_cache.insert( Meta::valAlbumGainPeak, value ); break; } commitIfInNonBatchUpdate(); } } void SqlTrack::beginUpdate() { QWriteLocker locker( &m_lock ); m_batchUpdate++; } void SqlTrack::endUpdate() { QWriteLocker locker( &m_lock ); Q_ASSERT( m_batchUpdate > 0 ); m_batchUpdate--; commitIfInNonBatchUpdate(); } void SqlTrack::commitIfInNonBatchUpdate( qint64 field, const QVariant &value ) { m_cache.insert( field, value ); commitIfInNonBatchUpdate(); } void SqlTrack::commitIfInNonBatchUpdate() { if( m_batchUpdate > 0 || m_cache.isEmpty() ) return; // nothing to do // debug() << "SqlTrack::commitMetaDataChanges " << m_cache; QString oldUid = m_uid; // for all the following objects we need to invalidate the cache and // notify the observers after the update KSharedPtr oldArtist; KSharedPtr newArtist; KSharedPtr oldAlbum; KSharedPtr newAlbum; KSharedPtr oldComposer; KSharedPtr newComposer; KSharedPtr oldGenre; KSharedPtr newGenre; KSharedPtr oldYear; KSharedPtr newYear; if( m_cache.contains( Meta::valFormat ) ) m_filetype = Amarok::FileType(m_cache.value( Meta::valFormat ).toInt()); if( m_cache.contains( Meta::valTitle ) ) m_title = m_cache.value( Meta::valTitle ).toString(); if( m_cache.contains( Meta::valComment ) ) m_comment = m_cache.value( Meta::valComment ).toString(); if( m_cache.contains( Meta::valScore ) ) m_score = m_cache.value( Meta::valScore ).toDouble(); if( m_cache.contains( Meta::valRating ) ) m_rating = m_cache.value( Meta::valRating ).toInt(); if( m_cache.contains( Meta::valLength ) ) m_length = m_cache.value( Meta::valLength ).toLongLong(); if( m_cache.contains( Meta::valSamplerate ) ) m_sampleRate = m_cache.value( Meta::valSamplerate ).toInt(); if( m_cache.contains( Meta::valBitrate ) ) m_bitrate = m_cache.value( Meta::valBitrate ).toInt(); if( m_cache.contains( Meta::valFirstPlayed ) ) m_firstPlayed = m_cache.value( Meta::valFirstPlayed ).toDateTime(); if( m_cache.contains( Meta::valLastPlayed ) ) m_lastPlayed = m_cache.value( Meta::valLastPlayed ).toDateTime(); if( m_cache.contains( Meta::valTrackNr ) ) m_trackNumber = m_cache.value( Meta::valTrackNr ).toInt(); if( m_cache.contains( Meta::valDiscNr ) ) m_discNumber = m_cache.value( Meta::valDiscNr ).toInt(); if( m_cache.contains( Meta::valPlaycount ) ) m_playCount = m_cache.value( Meta::valPlaycount ).toInt(); if( m_cache.contains( Meta::valCreateDate ) ) m_createDate = m_cache.value( Meta::valCreateDate ).toDateTime(); if( m_cache.contains( Meta::valModified ) ) m_modifyDate = m_cache.value( Meta::valModified ).toDateTime(); if( m_cache.contains( Meta::valTrackGain ) ) m_trackGain = m_cache.value( Meta::valTrackGain ).toDouble(); if( m_cache.contains( Meta::valTrackGainPeak ) ) m_trackPeakGain = m_cache.value( Meta::valTrackGainPeak ).toDouble(); if( m_cache.contains( Meta::valAlbumGain ) ) m_albumGain = m_cache.value( Meta::valAlbumGain ).toDouble(); if( m_cache.contains( Meta::valAlbumGainPeak ) ) m_albumPeakGain = m_cache.value( Meta::valAlbumGainPeak ).toDouble(); if( m_cache.contains( Meta::valUrl ) ) { // slight problem here: it is possible to set the url to the one of an already // existing track, which is forbidden by the database // At least the ScanResultProcessor handles this problem - KUrl oldUrl = m_url; - KUrl newUrl = m_cache.value( Meta::valUrl ).toString(); + QUrl oldUrl = m_url; + QUrl newUrl = m_cache.value( Meta::valUrl ).toString(); if( oldUrl != newUrl ) m_collection->registry()->updateCachedUrl( oldUrl.path(), newUrl.path() ); m_url = newUrl; // debug() << "m_cache contains a new URL, setting m_url to " << m_url << " from " << oldUrl; } if( m_cache.contains( Meta::valArtist ) ) { //invalidate cache of the old artist... oldArtist = static_cast(m_artist.data()); m_artist = m_collection->registry()->getArtist( m_cache.value( Meta::valArtist ).toString() ); //and the new one newArtist = static_cast(m_artist.data()); // if the current album is no compilation and we aren't changing // the album anyway, then we need to create a new album with the // new artist. if( m_album ) { bool supp = m_album->suppressImageAutoFetch(); m_album->setSuppressImageAutoFetch( true ); if( m_album->hasAlbumArtist() && m_album->albumArtist() == oldArtist && !m_cache.contains( Meta::valAlbum ) && !m_cache.contains( Meta::valAlbumId ) ) { m_cache.insert( Meta::valAlbum, m_album->name() ); } m_album->setSuppressImageAutoFetch( supp ); } } if( m_cache.contains( Meta::valAlbum ) || m_cache.contains( Meta::valAlbumId ) || m_cache.contains( Meta::valAlbumArtist ) ) { oldAlbum = static_cast(m_album.data()); if( m_cache.contains( Meta::valAlbumId ) ) m_album = m_collection->registry()->getAlbum( m_cache.value( Meta::valAlbumId ).toInt() ); else { // the album should remain a compilation after renaming it // TODO: we would need to use the artist helper QString newArtistName; if( m_cache.contains( Meta::valAlbumArtist ) ) newArtistName = m_cache.value( Meta::valAlbumArtist ).toString(); else if( oldAlbum && oldAlbum->isCompilation() && !oldAlbum->name().isEmpty() ) newArtistName.clear(); else if( oldAlbum && oldAlbum->hasAlbumArtist() ) newArtistName = oldAlbum->albumArtist()->name(); m_album = m_collection->registry()->getAlbum( m_cache.contains( Meta::valAlbum) ? m_cache.value( Meta::valAlbum ).toString() : oldAlbum->name(), newArtistName ); } newAlbum = static_cast(m_album.data()); // due to the complex logic with artist and albumId it can happen that // in the end we have the same album as before. if( newAlbum == oldAlbum ) { m_cache.remove( Meta::valAlbum ); m_cache.remove( Meta::valAlbumId ); m_cache.remove( Meta::valAlbumArtist ); oldAlbum.clear(); newAlbum.clear(); } } if( m_cache.contains( Meta::valComposer ) ) { oldComposer = static_cast(m_composer.data()); m_composer = m_collection->registry()->getComposer( m_cache.value( Meta::valComposer ).toString() ); newComposer = static_cast(m_composer.data()); } if( m_cache.contains( Meta::valGenre ) ) { oldGenre = static_cast(m_genre.data()); m_genre = m_collection->registry()->getGenre( m_cache.value( Meta::valGenre ).toString() ); newGenre = static_cast(m_genre.data()); } if( m_cache.contains( Meta::valYear ) ) { oldYear = static_cast(m_year.data()); m_year = m_collection->registry()->getYear( m_cache.value( Meta::valYear ).toInt() ); newYear = static_cast(m_year.data()); } if( m_cache.contains( Meta::valBpm ) ) m_bpm = m_cache.value( Meta::valBpm ).toDouble(); // --- write the file if( m_writeFile && AmarokConfig::writeBack() ) { Meta::Tag::writeTags( m_url.path(), m_cache, AmarokConfig::writeBackStatistics() ); // unique id may have changed QString uid = Meta::Tag::readTags( m_url.path() ).value( Meta::valUniqueId ).toString(); if( !uid.isEmpty() ) m_cache[ Meta::valUniqueId ] = m_collection->generateUidUrl( uid ); } // needs to be after writing to file; that may have changed generated uid if( m_cache.contains( Meta::valUniqueId ) ) { QString newUid = m_cache.value( Meta::valUniqueId ).toString(); if( oldUid != newUid && m_collection->registry()->updateCachedUid( oldUid, newUid ) ) m_uid = newUid; } //updating the fields might have changed the filesize //read the current filesize so that we can update the db QFile file( m_url.path() ); if( file.exists() ) { if( m_filesize != file.size() ) { m_cache.insert( Meta::valFilesize, file.size() ); m_filesize = file.size(); } } // --- add to the registry dirty list SqlRegistry *registry = 0; // prevent writing to the db when we don't know the directory, bug 322474. Note that // m_urlId is created by registry->commitDirtyTracks() if there is none. if( m_deviceId != 0 && m_directoryId > 0 ) { registry = m_collection->registry(); QMutexLocker locker2( ®istry->m_blockMutex ); registry->m_dirtyTracks.insert( Meta::SqlTrackPtr( this ) ); if( oldArtist ) registry->m_dirtyArtists.insert( oldArtist ); if( newArtist ) registry->m_dirtyArtists.insert( newArtist ); if( oldAlbum ) registry->m_dirtyAlbums.insert( oldAlbum ); if( newAlbum ) registry->m_dirtyAlbums.insert( newAlbum ); if( oldComposer ) registry->m_dirtyComposers.insert( oldComposer ); if( newComposer ) registry->m_dirtyComposers.insert( newComposer ); if( oldGenre ) registry->m_dirtyGenres.insert( oldGenre ); if( newGenre ) registry->m_dirtyGenres.insert( newGenre ); if( oldYear ) registry->m_dirtyYears.insert( oldYear ); if( newYear ) registry->m_dirtyYears.insert( newYear ); } else error() << Q_FUNC_INFO << "non-positive urlId, zero deviceId or non-positive" << "directoryId encountered in track" << m_url << "urlId:" << m_urlId << "deviceId:" << m_deviceId << "directoryId:" << m_directoryId << "- not writing back metadata" << "changes to the database."; m_lock.unlock(); // or else we provoke a deadlock // copy the image BUG: 203211 (we need to do it here or provoke a dead lock) if( oldAlbum && newAlbum ) { bool oldSupp = oldAlbum->suppressImageAutoFetch(); bool newSupp = newAlbum->suppressImageAutoFetch(); oldAlbum->setSuppressImageAutoFetch( true ); newAlbum->setSuppressImageAutoFetch( true ); if( oldAlbum->hasImage() && !newAlbum->hasImage() ) newAlbum->setImage( oldAlbum->imageLocation().path() ); oldAlbum->setSuppressImageAutoFetch( oldSupp ); newAlbum->setSuppressImageAutoFetch( newSupp ); } if( registry ) registry->commitDirtyTracks(); // calls notifyObservers() as appropriate else notifyObservers(); m_lock.lockForWrite(); // reset back to state it was during call if( m_uid != oldUid ) { updatePlaylistsToDb( m_cache, oldUid ); updateEmbeddedCoversToDb( m_cache, oldUid ); } // --- clean up m_cache.clear(); } void SqlTrack::updatePlaylistsToDb( const FieldHash &fields, const QString &oldUid ) { if( fields.isEmpty() ) return; // nothing to do SqlStorage *storage = m_collection->sqlStorage(); QStringList tags; // keep this in sync with SqlPlaylist::saveTracks()! if( fields.contains( Meta::valUrl ) ) tags << QString( "url='%1'" ).arg( storage->escape( m_url.path() ) ); if( fields.contains( Meta::valTitle ) ) tags << QString( "title='%1'" ).arg( storage->escape( m_title ) ); if( fields.contains( Meta::valAlbum ) ) tags << QString( "album='%1'" ).arg( m_album ? storage->escape( m_album->prettyName() ) : "" ); if( fields.contains( Meta::valArtist ) ) tags << QString( "artist='%1'" ).arg( m_artist ? storage->escape( m_artist->prettyName() ) : "" ); if( fields.contains( Meta::valLength ) ) tags << QString( "length=%1").arg( QString::number( m_length ) ); if( fields.contains( Meta::valUniqueId ) ) { // SqlPlaylist mirrors uniqueid to url, update it too, bug 312128 tags << QString( "url='%1'" ).arg( storage->escape( m_uid ) ); tags << QString( "uniqueid='%1'" ).arg( storage->escape( m_uid ) ); } if( !tags.isEmpty() ) { QString update = "UPDATE playlist_tracks SET %1 WHERE uniqueid = '%2';"; update = update.arg( tags.join( ", " ), storage->escape( oldUid ) ); storage->query( update ); } } void SqlTrack::updateEmbeddedCoversToDb( const FieldHash &fields, const QString &oldUid ) { if( fields.isEmpty() ) return; // nothing to do SqlStorage *storage = m_collection->sqlStorage(); QString tags; if( fields.contains( Meta::valUniqueId ) ) tags += QString( ",path='%1'" ).arg( storage->escape( m_uid ) ); if( !tags.isEmpty() ) { tags = tags.remove(0, 1); // the first character is always a ',' QString update = "UPDATE images SET %1 WHERE path = '%2';"; update = update.arg( tags, storage->escape( oldUid ) ); storage->query( update ); } } QString SqlTrack::prettyTitle( const QString &filename ) //static { QString s = filename; //just so the code is more readable //remove .part extension if it exists if (s.endsWith( ".part" )) s = s.left( s.length() - 5 ); //remove file extension, s/_/ /g and decode %2f-like sequences s = s.left( s.lastIndexOf( '.' ) ).replace( '_', ' ' ); - s = KUrl::fromPercentEncoding( s.toAscii() ); + s = QUrl::fromPercentEncoding( s.toAscii() ); return s; } bool SqlTrack::inCollection() const { QReadLocker locker( &m_lock ); return m_trackId > 0; } Collections::Collection* SqlTrack::collection() const { return m_collection; } QString SqlTrack::cachedLyrics() const { /* We don't cache the string as it may be potentially very long */ QString query = QString( "SELECT lyrics FROM lyrics WHERE url = %1" ).arg( m_urlId ); QStringList result = m_collection->sqlStorage()->query( query ); if( result.isEmpty() ) return QString(); return result.first(); } void SqlTrack::setCachedLyrics( const QString &lyrics ) { QString query = QString( "SELECT count(*) FROM lyrics WHERE url = %1").arg( m_urlId ); const QStringList queryResult = m_collection->sqlStorage()->query( query ); if( queryResult.isEmpty() ) return; // error in the query? if( queryResult.first().toInt() == 0 ) { QString insert = QString( "INSERT INTO lyrics( url, lyrics ) VALUES ( %1, '%2' )" ) .arg( QString::number( m_urlId ), m_collection->sqlStorage()->escape( lyrics ) ); m_collection->sqlStorage()->insert( insert, "lyrics" ); } else { QString update = QString( "UPDATE lyrics SET lyrics = '%1' WHERE url = %2" ) .arg( m_collection->sqlStorage()->escape( lyrics ), QString::number( m_urlId ) ); m_collection->sqlStorage()->query( update ); } notifyObservers(); } bool SqlTrack::hasCapabilityInterface( Capabilities::Capability::Type type ) const { switch( type ) { case Capabilities::Capability::Actions: case Capabilities::Capability::Organisable: case Capabilities::Capability::BookmarkThis: case Capabilities::Capability::WriteTimecode: case Capabilities::Capability::LoadTimecode: case Capabilities::Capability::ReadLabel: case Capabilities::Capability::WriteLabel: case Capabilities::Capability::FindInSource: return true; default: return Track::hasCapabilityInterface( type ); } } Capabilities::Capability* SqlTrack::createCapabilityInterface( Capabilities::Capability::Type type ) { switch( type ) { case Capabilities::Capability::Actions: { QList actions; //TODO These actions will hang around until m_collection is destructed. // Find a better parent to avoid this memory leak. //actions.append( new CopyToDeviceAction( m_collection, this ) ); return new Capabilities::ActionsCapability( actions ); } case Capabilities::Capability::Organisable: return new Capabilities::OrganiseCapabilityImpl( this ); case Capabilities::Capability::BookmarkThis: return new Capabilities::BookmarkThisCapability( new BookmarkCurrentTrackPositionAction( 0 ) ); case Capabilities::Capability::WriteTimecode: return new Capabilities::TimecodeWriteCapabilityImpl( this ); case Capabilities::Capability::LoadTimecode: return new Capabilities::TimecodeLoadCapabilityImpl( this ); case Capabilities::Capability::ReadLabel: return new Capabilities::SqlReadLabelCapability( this, sqlCollection()->sqlStorage() ); case Capabilities::Capability::WriteLabel: return new Capabilities::SqlWriteLabelCapability( this, sqlCollection()->sqlStorage() ); case Capabilities::Capability::FindInSource: return new Capabilities::FindInSourceCapabilityImpl( this ); default: return Track::createCapabilityInterface( type ); } } void SqlTrack::addLabel( const QString &label ) { Meta::LabelPtr realLabel = m_collection->registry()->getLabel( label ); addLabel( realLabel ); } void SqlTrack::addLabel( const Meta::LabelPtr &label ) { KSharedPtr sqlLabel = KSharedPtr::dynamicCast( label ); if( !sqlLabel ) { Meta::LabelPtr tmp = m_collection->registry()->getLabel( label->name() ); sqlLabel = KSharedPtr::dynamicCast( tmp ); } if( sqlLabel ) { QWriteLocker locker( &m_lock ); commitIfInNonBatchUpdate(); // we need to have a up-to-date m_urlId if( m_urlId <= 0 ) { warning() << "Track does not have an urlId."; return; } QString countQuery = "SELECT COUNT(*) FROM urls_labels WHERE url = %1 AND label = %2;"; QStringList countRs = m_collection->sqlStorage()->query( countQuery.arg( QString::number( m_urlId ), QString::number( sqlLabel->id() ) ) ); if( !countRs.isEmpty() && countRs.first().toInt() == 0 ) { QString insert = "INSERT INTO urls_labels(url,label) VALUES (%1,%2);"; m_collection->sqlStorage()->insert( insert.arg( QString::number( m_urlId ), QString::number( sqlLabel->id() ) ), "urls_labels" ); if( m_labelsInCache ) { m_labelsCache.append( Meta::LabelPtr::staticCast( sqlLabel ) ); } locker.unlock(); notifyObservers(); sqlLabel->invalidateCache(); } } } int SqlTrack::id() const { QReadLocker locker( &m_lock ); return m_trackId; } int SqlTrack::urlId() const { QReadLocker locker( &m_lock ); return m_urlId; } void SqlTrack::removeLabel( const Meta::LabelPtr &label ) { KSharedPtr sqlLabel = KSharedPtr::dynamicCast( label ); if( !sqlLabel ) { Meta::LabelPtr tmp = m_collection->registry()->getLabel( label->name() ); sqlLabel = KSharedPtr::dynamicCast( tmp ); } if( sqlLabel ) { QString query = "DELETE FROM urls_labels WHERE label = %2 and url = (SELECT url FROM tracks WHERE id = %1);"; m_collection->sqlStorage()->query( query.arg( QString::number( m_trackId ), QString::number( sqlLabel->id() ) ) ); if( m_labelsInCache ) { m_labelsCache.removeAll( Meta::LabelPtr::staticCast( sqlLabel ) ); } notifyObservers(); sqlLabel->invalidateCache(); } } Meta::LabelList SqlTrack::labels() const { { QReadLocker locker( &m_lock ); if( m_labelsInCache ) return m_labelsCache; } if( !m_collection ) return Meta::LabelList(); // when running the query maker don't lock. might lead to deadlock via registry Collections::SqlQueryMaker *qm = static_cast< Collections::SqlQueryMaker* >( m_collection->queryMaker() ); qm->setQueryType( Collections::QueryMaker::Label ); qm->addMatch( Meta::TrackPtr( const_cast(this) ) ); qm->setBlocking( true ); qm->run(); { QWriteLocker locker( &m_lock ); m_labelsInCache = true; m_labelsCache = qm->labels(); delete qm; return m_labelsCache; } } TrackEditorPtr SqlTrack::editor() { return TrackEditorPtr( isEditable() ? this : 0 ); } StatisticsPtr SqlTrack::statistics() { return StatisticsPtr( this ); } void SqlTrack::remove() { QWriteLocker locker( &m_lock ); m_cache.clear(); locker.unlock(); m_collection->registry()->removeTrack( m_urlId, m_uid ); // -- inform all albums, artist, years #undef foreachInvalidateCache #define INVALIDATE_AND_UPDATE(X) if( X ) \ { \ X->invalidateCache(); \ X->notifyObservers(); \ } INVALIDATE_AND_UPDATE(static_cast(m_artist.data())); INVALIDATE_AND_UPDATE(static_cast(m_album.data())); INVALIDATE_AND_UPDATE(static_cast(m_composer.data())); INVALIDATE_AND_UPDATE(static_cast(m_genre.data())); INVALIDATE_AND_UPDATE(static_cast(m_year.data())); #undef INVALIDATE_AND_UPDATE m_artist = 0; m_album = 0; m_composer = 0; m_genre = 0; m_year = 0; m_urlId = 0; m_trackId = 0; m_statisticsId = 0; m_collection->collectionUpdated(); } //---------------------- class Artist -------------------------- SqlArtist::SqlArtist( Collections::SqlCollection *collection, int id, const QString &name ) : Artist() , m_collection( collection ) , m_id( id ) , m_name( name ) , m_tracksLoaded( false ) { Q_ASSERT( m_collection ); Q_ASSERT( m_id > 0 ); } Meta::SqlArtist::~SqlArtist() { } void SqlArtist::invalidateCache() { QMutexLocker locker( &m_mutex ); m_tracksLoaded = false; m_tracks.clear(); } TrackList SqlArtist::tracks() { { QMutexLocker locker( &m_mutex ); if( m_tracksLoaded ) return m_tracks; } // when running the query maker don't lock. might lead to deadlock via registry Collections::SqlQueryMaker *qm = static_cast< Collections::SqlQueryMaker* >( m_collection->queryMaker() ); qm->setQueryType( Collections::QueryMaker::Track ); qm->addMatch( Meta::ArtistPtr( this ) ); qm->setBlocking( true ); qm->run(); { QMutexLocker locker( &m_mutex ); m_tracks = qm->tracks(); m_tracksLoaded = true; delete qm; return m_tracks; } } bool SqlArtist::hasCapabilityInterface( Capabilities::Capability::Type type ) const { switch( type ) { case Capabilities::Capability::BookmarkThis: return true; default: return Artist::hasCapabilityInterface( type ); } } Capabilities::Capability* SqlArtist::createCapabilityInterface( Capabilities::Capability::Type type ) { switch( type ) { case Capabilities::Capability::BookmarkThis: return new Capabilities::BookmarkThisCapability( new BookmarkArtistAction( 0, Meta::ArtistPtr( this ) ) ); default: return Artist::createCapabilityInterface( type ); } } //--------------- class Album --------------------------------- const QString SqlAlbum::AMAROK_UNSET_MAGIC = QString( "AMAROK_UNSET_MAGIC" ); SqlAlbum::SqlAlbum( Collections::SqlCollection *collection, int id, const QString &name, int artist ) : Album() , m_collection( collection ) , m_name( name ) , m_id( id ) , m_artistId( artist ) , m_imageId( -1 ) , m_hasImage( false ) , m_hasImageChecked( false ) , m_unsetImageId( -1 ) , m_tracksLoaded( false ) , m_suppressAutoFetch( false ) , m_mutex( QMutex::Recursive ) { Q_ASSERT( m_collection ); Q_ASSERT( m_id > 0 ); } Meta::SqlAlbum::~SqlAlbum() { CoverCache::invalidateAlbum( this ); } void SqlAlbum::invalidateCache() { QMutexLocker locker( &m_mutex ); m_tracksLoaded = false; m_hasImage = false; m_hasImageChecked = false; m_tracks.clear(); } TrackList SqlAlbum::tracks() { { QMutexLocker locker( &m_mutex ); if( m_tracksLoaded ) return m_tracks; } // when running the query maker don't lock. might lead to deadlock via registry Collections::SqlQueryMaker *qm = static_cast< Collections::SqlQueryMaker* >( m_collection->queryMaker() ); qm->setQueryType( Collections::QueryMaker::Track ); qm->addMatch( Meta::AlbumPtr( this ) ); qm->orderBy( Meta::valDiscNr ); qm->orderBy( Meta::valTrackNr ); qm->orderBy( Meta::valTitle ); qm->setBlocking( true ); qm->run(); { QMutexLocker locker( &m_mutex ); m_tracks = qm->tracks(); m_tracksLoaded = true; delete qm; return m_tracks; } } // note for internal implementation: // if hasImage returns true then m_imagePath is set bool SqlAlbum::hasImage( int size ) const { Q_UNUSED(size); // we have every size if we have an image at all QMutexLocker locker( &m_mutex ); if( m_name.isEmpty() ) return false; if( !m_hasImageChecked ) { m_hasImageChecked = true; const_cast( this )->largeImagePath(); // The user has explicitly set no cover if( m_imagePath == AMAROK_UNSET_MAGIC ) m_hasImage = false; // if we don't have an image but it was not explicitly blocked else if( m_imagePath.isEmpty() ) { // Cover fetching runs in another thread. If there is a retrieved cover // then updateImage() gets called which updates the cache and alerts the // subscribers. We use queueAlbum() because this runs the fetch as a // background job and doesn't give an intruding popup asking for confirmation if( !m_suppressAutoFetch && !m_name.isEmpty() && AmarokConfig::autoGetCoverArt() ) CoverFetcher::instance()->queueAlbum( AlbumPtr(const_cast(this)) ); m_hasImage = false; } else m_hasImage = true; } return m_hasImage; } QImage SqlAlbum::image( int size ) const { QMutexLocker locker( &m_mutex ); if( !hasImage() ) return Meta::Album::image( size ); // findCachedImage looks for a scaled version of the fullsize image // which may have been saved on a previous lookup QString cachedImagePath; if( size <= 1 ) cachedImagePath = m_imagePath; else cachedImagePath = scaledDiskCachePath( size ); //FIXME this cache doesn't differentiate between shadowed/unshadowed // a image exists. just load it. if( !cachedImagePath.isEmpty() && QFile( cachedImagePath ).exists() ) { QImage image( cachedImagePath ); if( image.isNull() ) return Meta::Album::image( size ); return image; } // no cached scaled image exists. Have to create it QImage image; // --- embedded cover if( m_collection && m_imagePath.startsWith( m_collection->uidUrlProtocol() ) ) { // -- check if we have a track with the given path as uid Meta::TrackPtr track = m_collection->getTrackFromUid( m_imagePath ); if( track ) image = Meta::Tag::embeddedCover( track->playableUrl().path() ); } // --- a normal path if( image.isNull() ) image = QImage( m_imagePath ); if( image.isNull() ) return Meta::Album::image( size ); if( size > 1 && size < 1000 ) { image = image.scaled( size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation ); image.save( cachedImagePath, "PNG" ); } return image; } -KUrl +QUrl SqlAlbum::imageLocation( int size ) { if( !hasImage() ) - return KUrl(); + return QUrl(); // findCachedImage looks for a scaled version of the fullsize image // which may have been saved on a previous lookup if( size <= 1 ) return m_imagePath; QString cachedImagePath = scaledDiskCachePath( size ); if( cachedImagePath.isEmpty() ) - return KUrl(); + return QUrl(); if( !QFile( cachedImagePath ).exists() ) { // If we don't have the location, it's possible that we haven't tried to find the image yet // So, let's look for it and just ignore the result QImage i = image( size ); Q_UNUSED( i ) } if( !QFile( cachedImagePath ).exists() ) - return KUrl(); + return QUrl(); return cachedImagePath; } void SqlAlbum::setImage( const QImage &image ) { // the unnamed album is special. it will never have an image if( m_name.isEmpty() ) return; QMutexLocker locker( &m_mutex ); if( image.isNull() ) return; // removeImage() will destroy all scaled cached versions of the artwork // and remove references from the database if required. removeImage(); QString path = largeDiskCachePath(); // make sure not to overwrite existing images while( QFile(path).exists() ) path += '_'; // not that nice but it shouldn't happen that often. image.save( path, "JPG" ); setImage( path ); locker.unlock(); notifyObservers(); // -- write back the album cover if allowed if( AmarokConfig::writeBackCover() ) { // - scale to cover to a sensible size QImage scaledImage( image ); if( scaledImage.width() > AmarokConfig::writeBackCoverDimensions() || scaledImage.height() > AmarokConfig::writeBackCoverDimensions() ) scaledImage = scaledImage.scaled( AmarokConfig::writeBackCoverDimensions(), AmarokConfig::writeBackCoverDimensions(), Qt::KeepAspectRatio, Qt::SmoothTransformation ); // - set the image for each track Meta::TrackList myTracks = tracks(); foreach( Meta::TrackPtr metaTrack, myTracks ) { // the song needs to be at least one mb big or we won't set an image // that means that the new image will increase the file size by less than 2% if( metaTrack->filesize() > 1024l * 1024l ) { Meta::FieldHash fields; fields.insert( Meta::valImage, scaledImage ); WriteTagsJob *job = new WriteTagsJob( metaTrack->playableUrl().path(), fields ); QObject::connect( job, SIGNAL(done(ThreadWeaver::Job*)), job, SLOT(deleteLater()) ); ThreadWeaver::Weaver::instance()->enqueue( job ); } // note: we might want to update the track file size after writing the image } } } void SqlAlbum::removeImage() { QMutexLocker locker( &m_mutex ); if( !hasImage() ) return; // Update the database image path // Set the album image to a magic value which will tell Amarok not to fetch it automatically const int unsetId = unsetImageId(); QString query = "UPDATE albums SET image = %1 WHERE id = %2"; m_collection->sqlStorage()->query( query.arg( QString::number( unsetId ), QString::number( m_id ) ) ); // From here on we check if there are any remaining references to that particular image in the database // If there aren't, then we should remove the image path from the database ( and possibly delete the file? ) // If there are, we need to leave it since other albums will reference this particular image path. // query = "SELECT count( albums.id ) FROM albums " "WHERE albums.image = %1"; QStringList res = m_collection->sqlStorage()->query( query.arg( QString::number( m_imageId ) ) ); if( !res.isEmpty() ) { int references = res.first().toInt(); // If there are no more references to this particular image, then we should clean up if( references <= 0 ) { query = "DELETE FROM images WHERE id = %1"; m_collection->sqlStorage()->query( query.arg( QString::number( m_imageId ) ) ); // remove the large cover only if it was cached. QDir largeCoverDir( Amarok::saveLocation( "albumcovers/large/" ) ); if( QFileInfo(m_imagePath).absoluteDir() == largeCoverDir ) QFile::remove( m_imagePath ); // remove all cache images QString key = md5sum( QString(), QString(), m_imagePath ); QDir cacheDir( Amarok::saveLocation( "albumcovers/cache/" ) ); QStringList cacheFilter; cacheFilter << QString( "*@" ) + key; QStringList cachedImages = cacheDir.entryList( cacheFilter ); foreach( const QString &image, cachedImages ) { bool r = QFile::remove( cacheDir.filePath( image ) ); debug() << "deleting cached image: " << image << " : " + ( r ? QString("ok") : QString("fail") ); } CoverCache::invalidateAlbum( this ); } } m_imageId = -1; m_imagePath.clear(); m_hasImage = false; m_hasImageChecked = true; locker.unlock(); notifyObservers(); } int SqlAlbum::unsetImageId() const { // Return the cached value if we have already done the lookup before if( m_unsetImageId >= 0 ) return m_unsetImageId; QString query = "SELECT id FROM images WHERE path = '%1'"; QStringList res = m_collection->sqlStorage()->query( query.arg( AMAROK_UNSET_MAGIC ) ); // We already have the AMAROK_UNSET_MAGIC variable in the database if( !res.isEmpty() ) { m_unsetImageId = res.first().toInt(); } else { // We need to create this value query = QString( "INSERT INTO images( path ) VALUES ( '%1' )" ) .arg( m_collection->sqlStorage()->escape( AMAROK_UNSET_MAGIC ) ); m_unsetImageId = m_collection->sqlStorage()->insert( query, "images" ); } return m_unsetImageId; } bool SqlAlbum::isCompilation() const { return !hasAlbumArtist(); } bool SqlAlbum::hasAlbumArtist() const { return !albumArtist().isNull(); } Meta::ArtistPtr SqlAlbum::albumArtist() const { if( m_artistId > 0 && !m_artist ) { const_cast( this )->m_artist = m_collection->registry()->getArtist( m_artistId ); } return m_artist; } QByteArray SqlAlbum::md5sum( const QString& artist, const QString& album, const QString& file ) const { // FIXME: names with unicode characters are not supported. // FIXME: "The Beatles"."Collection" and "The"."Beatles Collection" will produce the same hash. // FIXME: Correcting this now would invalidate all existing image stores. KMD5 context( artist.toLower().toLocal8Bit() + album.toLower().toLocal8Bit() + file.toLocal8Bit() ); return context.hexDigest(); } QString SqlAlbum::largeDiskCachePath() const { // IMPROVEMENT: the large disk cache path could be human readable const QString artist = hasAlbumArtist() ? albumArtist()->name() : QString(); if( artist.isEmpty() && m_name.isEmpty() ) return QString(); QDir largeCoverDir( Amarok::saveLocation( "albumcovers/large/" ) ); const QString key = md5sum( artist, m_name, QString() ); return largeCoverDir.filePath( key ); } QString SqlAlbum::scaledDiskCachePath( int size ) const { const QByteArray widthKey = QByteArray::number( size ) + '@'; QDir cacheCoverDir( Amarok::saveLocation( "albumcovers/cache/" ) ); QString key = md5sum( QString(), QString(), m_imagePath ); if( !cacheCoverDir.exists( widthKey + key ) ) { // the correct location is empty // check deprecated locations for the image cache and delete them // (deleting the scaled image cache is fine) const QString artist = hasAlbumArtist() ? albumArtist()->name() : QString(); if( artist.isEmpty() && m_name.isEmpty() ) ; // do nothing special else { QString oldKey; oldKey = md5sum( artist, m_name, m_imagePath ); if( cacheCoverDir.exists( widthKey + oldKey ) ) cacheCoverDir.remove( widthKey + oldKey ); oldKey = md5sum( artist, m_name, QString() ); if( cacheCoverDir.exists( widthKey + oldKey ) ) cacheCoverDir.remove( widthKey + oldKey ); } } return cacheCoverDir.filePath( widthKey + key ); } QString SqlAlbum::largeImagePath() { if( !m_collection ) return m_imagePath; // Look up in the database QString query = "SELECT images.id, images.path FROM images, albums WHERE albums.image = images.id AND albums.id = %1;"; // TODO: shouldn't we do a JOIN here? QStringList res = m_collection->sqlStorage()->query( query.arg( m_id ) ); if( !res.isEmpty() ) { m_imageId = res.at(0).toInt(); m_imagePath = res.at(1); // explicitly deleted image if( m_imagePath == AMAROK_UNSET_MAGIC ) return AMAROK_UNSET_MAGIC; // embedded image (e.g. id3v2 APIC // We store embedded images as unique ids in the database // we will get the real image later on from the track. if( m_imagePath.startsWith( m_collection->uidUrlProtocol()+"://" ) ) return m_imagePath; // normal file if( !m_imagePath.isEmpty() && QFile::exists( m_imagePath ) ) return m_imagePath; } // After a rescan we currently lose all image information, so we need // to check that we haven't already downloaded this image before. m_imagePath = largeDiskCachePath(); if( !m_imagePath.isEmpty() && QFile::exists( m_imagePath ) ) { setImage(m_imagePath); return m_imagePath; } m_imageId = -1; m_imagePath.clear(); return m_imagePath; } // note: we won't notify the observers. we are a private function. the caller must do that. void SqlAlbum::setImage( const QString &path ) { if( m_imagePath == path ) return; if( m_name.isEmpty() ) // the empty album never has an image return; QMutexLocker locker( &m_mutex ); QString imagePath = path; QString query = "SELECT id FROM images WHERE path = '%1'"; query = query.arg( m_collection->sqlStorage()->escape( imagePath ) ); QStringList res = m_collection->sqlStorage()->query( query ); if( res.isEmpty() ) { QString insert = QString( "INSERT INTO images( path ) VALUES ( '%1' )" ) .arg( m_collection->sqlStorage()->escape( imagePath ) ); m_imageId = m_collection->sqlStorage()->insert( insert, "images" ); } else m_imageId = res.first().toInt(); if( m_imageId >= 0 ) { query = QString("UPDATE albums SET image = %1 WHERE albums.id = %2" ) .arg( QString::number( m_imageId ), QString::number( m_id ) ); m_collection->sqlStorage()->query( query ); m_imagePath = imagePath; m_hasImage = true; m_hasImageChecked = true; CoverCache::invalidateAlbum( this ); } } /** Set the compilation flag. * Actually it does not cange this album but instead moves * the tracks to other albums (e.g. one with the same name which is a * compilation) * If the compilation flag is set to "false" then all songs * with different artists will be moved to other albums, possibly even * creating them. */ void SqlAlbum::setCompilation( bool compilation ) { if( m_name.isEmpty() ) return; if( isCompilation() == compilation ) { return; } else { m_collection->blockUpdatedSignal(); if( compilation ) { // get the new compilation album Meta::AlbumPtr metaAlbum = m_collection->registry()->getAlbum( name(), QString() ); KSharedPtr sqlAlbum = KSharedPtr::dynamicCast( metaAlbum ); Meta::FieldHash changes; changes.insert( Meta::valCompilation, 1); Meta::TrackList myTracks = tracks(); foreach( Meta::TrackPtr metaTrack, myTracks ) { SqlTrack* sqlTrack = static_cast(metaTrack.data()); // copy over the cover image if( sqlTrack->album()->hasImage() && !sqlAlbum->hasImage() ) sqlAlbum->setImage( sqlTrack->album()->imageLocation().path() ); // move the track sqlTrack->setAlbum( sqlAlbum->id() ); if( AmarokConfig::writeBack() ) Meta::Tag::writeTags( sqlTrack->playableUrl().path(), changes, AmarokConfig::writeBackStatistics() ); } /* TODO: delete all old tracks albums */ } else { Meta::FieldHash changes; changes.insert( Meta::valCompilation, 0); Meta::TrackList myTracks = tracks(); foreach( Meta::TrackPtr metaTrack, myTracks ) { SqlTrack* sqlTrack = static_cast(metaTrack.data()); Meta::ArtistPtr trackArtist = sqlTrack->artist(); // get the new album Meta::AlbumPtr metaAlbum = m_collection->registry()->getAlbum( sqlTrack->album()->name(), trackArtist ? ArtistHelper::realTrackArtist( trackArtist->name() ) : QString() ); KSharedPtr sqlAlbum = KSharedPtr::dynamicCast( metaAlbum ); // copy over the cover image if( sqlTrack->album()->hasImage() && !sqlAlbum->hasImage() ) sqlAlbum->setImage( sqlTrack->album()->imageLocation().path() ); // move the track sqlTrack->setAlbum( sqlAlbum->id() ); if( AmarokConfig::writeBack() ) Meta::Tag::writeTags( sqlTrack->playableUrl().path(), changes, AmarokConfig::writeBackStatistics() ); } /* TODO //step 5: delete the original album, if necessary */ } m_collection->unblockUpdatedSignal(); } } bool SqlAlbum::hasCapabilityInterface( Capabilities::Capability::Type type ) const { if( m_name.isEmpty() ) return false; switch( type ) { case Capabilities::Capability::Actions: case Capabilities::Capability::BookmarkThis: return true; default: return Album::hasCapabilityInterface( type ); } } Capabilities::Capability* SqlAlbum::createCapabilityInterface( Capabilities::Capability::Type type ) { if( m_name.isEmpty() ) return 0; switch( type ) { case Capabilities::Capability::Actions: return new Capabilities::AlbumActionsCapability( Meta::AlbumPtr( this ) ); case Capabilities::Capability::BookmarkThis: return new Capabilities::BookmarkThisCapability( new BookmarkAlbumAction( 0, Meta::AlbumPtr( this ) ) ); default: return Album::createCapabilityInterface( type ); } } //---------------SqlComposer--------------------------------- SqlComposer::SqlComposer( Collections::SqlCollection *collection, int id, const QString &name ) : Composer() , m_collection( collection ) , m_id( id ) , m_name( name ) , m_tracksLoaded( false ) { Q_ASSERT( m_collection ); Q_ASSERT( m_id > 0 ); } void SqlComposer::invalidateCache() { QMutexLocker locker( &m_mutex ); m_tracksLoaded = false; m_tracks.clear(); } TrackList SqlComposer::tracks() { { QMutexLocker locker( &m_mutex ); if( m_tracksLoaded ) return m_tracks; } Collections::SqlQueryMaker *qm = static_cast< Collections::SqlQueryMaker* >( m_collection->queryMaker() ); qm->setQueryType( Collections::QueryMaker::Track ); qm->addMatch( Meta::ComposerPtr( this ) ); qm->setBlocking( true ); qm->run(); { QMutexLocker locker( &m_mutex ); m_tracks = qm->tracks(); m_tracksLoaded = true; delete qm; return m_tracks; } } //---------------SqlGenre--------------------------------- SqlGenre::SqlGenre( Collections::SqlCollection *collection, int id, const QString &name ) : Genre() , m_collection( collection ) , m_id( id ) , m_name( name ) , m_tracksLoaded( false ) { Q_ASSERT( m_collection ); Q_ASSERT( m_id > 0 ); } void SqlGenre::invalidateCache() { QMutexLocker locker( &m_mutex ); m_tracksLoaded = false; m_tracks.clear(); } TrackList SqlGenre::tracks() { { QMutexLocker locker( &m_mutex ); if( m_tracksLoaded ) return m_tracks; } // when running the query maker don't lock. might lead to deadlock via registry Collections::SqlQueryMaker *qm = static_cast< Collections::SqlQueryMaker* >( m_collection->queryMaker() ); qm->setQueryType( Collections::QueryMaker::Track ); qm->addMatch( Meta::GenrePtr( this ) ); qm->setBlocking( true ); qm->run(); { QMutexLocker locker( &m_mutex ); m_tracks = qm->tracks(); m_tracksLoaded = true; delete qm; return m_tracks; } } //---------------SqlYear--------------------------------- SqlYear::SqlYear( Collections::SqlCollection *collection, int id, int year) : Year() , m_collection( collection ) , m_id( id ) , m_year( year ) , m_tracksLoaded( false ) { Q_ASSERT( m_collection ); Q_ASSERT( m_id > 0 ); } void SqlYear::invalidateCache() { QMutexLocker locker( &m_mutex ); m_tracksLoaded = false; m_tracks.clear(); } TrackList SqlYear::tracks() { { QMutexLocker locker( &m_mutex ); if( m_tracksLoaded ) return m_tracks; } // when running the query maker don't lock. might lead to deadlock via registry Collections::SqlQueryMaker *qm = static_cast< Collections::SqlQueryMaker* >( m_collection->queryMaker() ); qm->setQueryType( Collections::QueryMaker::Track ); qm->addMatch( Meta::YearPtr( this ) ); qm->setBlocking( true ); qm->run(); { QMutexLocker locker( &m_mutex ); m_tracks = qm->tracks(); m_tracksLoaded = true; delete qm; return m_tracks; } } //---------------SqlLabel--------------------------------- SqlLabel::SqlLabel( Collections::SqlCollection *collection, int id, const QString &name ) : Label() , m_collection( collection ) , m_id( id ) , m_name( name ) , m_tracksLoaded( false ) { Q_ASSERT( m_collection ); Q_ASSERT( m_id > 0 ); } void SqlLabel::invalidateCache() { QMutexLocker locker( &m_mutex ); m_tracksLoaded = false; m_tracks.clear(); } TrackList SqlLabel::tracks() { { QMutexLocker locker( &m_mutex ); if( m_tracksLoaded ) return m_tracks; } // when running the query maker don't lock. might lead to deadlock via registry Collections::SqlQueryMaker *qm = static_cast< Collections::SqlQueryMaker* >( m_collection->queryMaker() ); qm->setQueryType( Collections::QueryMaker::Track ); qm->addMatch( Meta::LabelPtr( this ) ); qm->setBlocking( true ); qm->run(); { QMutexLocker locker( &m_mutex ); m_tracks = qm->tracks(); m_tracksLoaded = true; delete qm; return m_tracks; } } diff --git a/src/core-impl/collections/db/sql/SqlMeta.h b/src/core-impl/collections/db/sql/SqlMeta.h index 407fd72581..8a2e79c6fc 100644 --- a/src/core-impl/collections/db/sql/SqlMeta.h +++ b/src/core-impl/collections/db/sql/SqlMeta.h @@ -1,586 +1,586 @@ /**************************************************************************************** * Copyright (c) 2007 Maximilian Kossick * * Copyright (c) 2007 Alexandre Pereira de Oliveira * * Copyright (c) 2010 Ralf Engels * * * * 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, see . * ****************************************************************************************/ #ifndef SQLMETA_H #define SQLMETA_H #include "core/meta/Meta.h" #include "core/meta/Statistics.h" #include "core/meta/TrackEditor.h" #include "core/meta/support/MetaConstants.h" #include "amarok_sqlcollection_export.h" #include "FileType.h" #include #include #include #include #include #include namespace Capabilities { class AlbumCapabilityDelegate; class ArtistCapabilityDelegate; class TrackCapabilityDelegate; } class QAction; class SqlRegistry; class TrackUrlsTableCommitter; class TrackTracksTableCommitter; class TrackStatisticsTableCommitter; namespace Collections { class SqlCollection; } class SqlScanResultProcessor; namespace Meta { /** The SqlTrack is a Meta::Track used by the SqlCollection. The SqlTrack has a couple of functions for writing values and also some functions for getting e.g. the track id used in the underlying database. However it is not recommended to interface with the database directly. The whole class should be thread save. */ class AMAROK_SQLCOLLECTION_EXPORT SqlTrack : public Track, public Statistics, public TrackEditor { public: /** Creates a new SqlTrack without. * Note that the trackId and urlId are empty meaning that this track * has no database representation until it's written first by setting * some of the meta information. * It is advisable to set at least the path. */ SqlTrack( Collections::SqlCollection *collection, int deviceId, const QString &rpath, int directoryId, const QString uidUrl ); SqlTrack( Collections::SqlCollection *collection, const QStringList &queryResult ); ~ SqlTrack(); virtual QString name() const; virtual QString prettyName() const; - virtual KUrl playableUrl() const; + virtual QUrl playableUrl() const; virtual QString prettyUrl() const; virtual QString uidUrl() const; virtual QString notPlayableReason() const; virtual Meta::AlbumPtr album() const; virtual Meta::ArtistPtr artist() const; virtual Meta::ComposerPtr composer() const; virtual Meta::YearPtr year() const; virtual Meta::GenrePtr genre() const; virtual QString type() const; virtual qreal bpm() const; virtual QString comment() const; virtual qint64 length() const; virtual int filesize() const; virtual int sampleRate() const; virtual int bitrate() const; virtual QDateTime createDate() const; virtual QDateTime modifyDate() const; virtual int trackNumber() const; virtual int discNumber() const; virtual qreal replayGain( Meta::ReplayGainTag mode ) const; virtual bool inCollection() const; virtual Collections::Collection* collection() const; virtual QString cachedLyrics() const; virtual void setCachedLyrics( const QString &lyrics ); virtual bool hasCapabilityInterface( Capabilities::Capability::Type type ) const; virtual Capabilities::Capability* createCapabilityInterface( Capabilities::Capability::Type type ); virtual void addLabel( const QString &label ); virtual void addLabel( const Meta::LabelPtr &label ); virtual void removeLabel( const Meta::LabelPtr &label ); virtual Meta::LabelList labels() const; virtual TrackEditorPtr editor(); virtual StatisticsPtr statistics(); // Meta::TrackEditor methods: virtual void setAlbum( const QString &newAlbum ); virtual void setAlbumArtist( const QString &newAlbumArtist ); virtual void setArtist( const QString &newArtist ); virtual void setComposer( const QString &newComposer ); virtual void setGenre( const QString &newGenre ); virtual void setYear( int newYear ); virtual void setTitle( const QString &newTitle ); virtual void setComment( const QString &newComment ); virtual void setTrackNumber( int newTrackNumber ); virtual void setDiscNumber( int newDiscNumber ); virtual void setBpm( const qreal newBpm ); // Meta::Statistics methods: virtual double score() const; virtual void setScore( double newScore ); virtual int rating() const; virtual void setRating( int newRating ); virtual QDateTime firstPlayed() const; virtual void setFirstPlayed( const QDateTime &newTime ); virtual QDateTime lastPlayed() const; virtual void setLastPlayed( const QDateTime &newTime ); virtual int playCount() const; virtual void setPlayCount( const int newCount ); // combined Meta::Statistics and Meta::TrackEditor methods: virtual void beginUpdate(); virtual void endUpdate(); // SqlTrack specific methods /** true if there is a collection, the file exists on disk and is writable */ bool isEditable() const; void setUidUrl( const QString &uid ); void setAlbum( int albumId ); void setType( Amarok::FileType newType ); void setLength( qint64 newLength ); void setSampleRate( int newSampleRate ); void setUrl( int deviceId, const QString &rpath, int directoryId ); void setBitrate( int newBitrate ); void setModifyDate( const QDateTime &newTime ); void setReplayGain( Meta::ReplayGainTag mode, qreal value ); /** Enables or disables writing changes to the file. * This function can be useful when changes are imported from the file. * In such a case writing the changes back again is stupid. */ virtual void setWriteFile( const bool enable ) { m_writeFile = enable; } int id() const; int urlId() const; Collections::SqlCollection* sqlCollection() const { return m_collection; } /** Does it's best to remove the track from database. * Considered that there is no signal that says "I am now removed" * this function still tries it's best to notify everyone * That the track is now removed, plus it will also delete it from * the database. */ void remove(); // SqlDatabase specific values /** Some numbers used in SqlRegistry. * Update if getTrackReturnValues is updated. */ enum TrackReturnIndex { returnIndex_urlId = 0, returnIndex_urlDeviceId = 1, returnIndex_urlRPath = 2, returnIndex_urlUid = 4, returnIndex_trackId = 5 }; // SqlDatabase specific values /** returns a string of all database values that can be fetched for a track */ static QString getTrackReturnValues(); /** returns the number of return values in getTrackReturnValues() */ static int getTrackReturnValueCount(); /** returns a string of all database joins that are required to fetch all values for a track*/ static QString getTrackJoinConditions(); protected: /** * Will commit all changes in m_cache if m_batch == 0. Must be called with m_lock * locked for writing. * * commitIfInNonBatchUpdate() will do three things: * 1. It will update the member variables. * 2. It will call all write methods * 3. It will notify all observers and the collection about the changes. */ void commitIfInNonBatchUpdate( qint64 field, const QVariant &value ); void commitIfInNonBatchUpdate(); void updatePlaylistsToDb( const FieldHash &fields, const QString &oldUid ); void updateEmbeddedCoversToDb( const FieldHash &fields, const QString &oldUid ); private: //helper functions static QString prettyTitle( const QString &filename ); Collections::SqlCollection* const m_collection; QString m_title; // the url table int m_urlId; int m_deviceId; QString m_rpath; int m_directoryId; // only set when the urls table needs to be written - KUrl m_url; + QUrl m_url; QString m_uid; // the rest int m_trackId; int m_statisticsId; qint64 m_length; qint64 m_filesize; int m_trackNumber; int m_discNumber; QDateTime m_lastPlayed; QDateTime m_firstPlayed; int m_playCount; int m_bitrate; int m_sampleRate; int m_rating; double m_score; QString m_comment; qreal m_bpm; qreal m_albumGain; qreal m_albumPeakGain; qreal m_trackGain; qreal m_trackPeakGain; QDateTime m_createDate; QDateTime m_modifyDate; Meta::AlbumPtr m_album; Meta::ArtistPtr m_artist; Meta::GenrePtr m_genre; Meta::ComposerPtr m_composer; Meta::YearPtr m_year; Amarok::FileType m_filetype; /** * Number of current batch operations started by @see beginUpdate() and not * yet ended by @see endUpdate(). Must only be accessed with m_lock held. */ int m_batchUpdate; bool m_writeFile; bool m_writeAllStatisticsFields; FieldHash m_cache; /** This ReadWriteLock is protecting all internal variables. It is ensuring that m_cache, m_batchUpdate and the othre internal variable are in a consistent state all the time. */ mutable QReadWriteLock m_lock; mutable bool m_labelsInCache; mutable Meta::LabelList m_labelsCache; friend class ::SqlRegistry; // needs to call notifyObservers friend class ::TrackUrlsTableCommitter; friend class ::TrackTracksTableCommitter; friend class ::TrackStatisticsTableCommitter; }; class AMAROK_SQLCOLLECTION_EXPORT SqlArtist : public Meta::Artist { public: SqlArtist( Collections::SqlCollection* collection, int id, const QString &name ); ~SqlArtist(); virtual QString name() const { return m_name; } virtual void invalidateCache(); virtual Meta::TrackList tracks(); virtual bool hasCapabilityInterface( Capabilities::Capability::Type type ) const; virtual Capabilities::Capability* createCapabilityInterface( Capabilities::Capability::Type type ); //SQL specific methods int id() const { return m_id; } private: Collections::SqlCollection* const m_collection; const int m_id; const QString m_name; bool m_tracksLoaded; Meta::TrackList m_tracks; QMutex m_mutex; friend class ::SqlRegistry; // needs to call notifyObservers friend class Meta::SqlTrack; // needs to call notifyObservers }; /** Represents an albums stored in the database. Note: The album without name is special. It will always be a compilation and never have a picture. */ class AMAROK_SQLCOLLECTION_EXPORT SqlAlbum : public Meta::Album { public: SqlAlbum( Collections::SqlCollection* collection, int id, const QString &name, int artist ); ~SqlAlbum(); virtual QString name() const { return m_name; } virtual void invalidateCache(); virtual Meta::TrackList tracks(); virtual bool isCompilation() const; virtual bool canUpdateCompilation() const { return true; } void setCompilation( bool compilation ); /** Returns true if this album has an artist. * The following equation is always true: isCompilation() != hasAlbumArtist() */ virtual bool hasAlbumArtist() const; /** Returns the album artist. * Note that setting the album artist is not supported. * A compilation does not have an artist and not only an empty artist. */ virtual Meta::ArtistPtr albumArtist() const; //updating album images is possible for local tracks, but let's ignore it for now /** Returns true if the album has a cover image. * @param size The maximum width or height of the result. * when size is <= 1, return the full size image */ virtual bool hasImage(int size = 0) const; virtual bool canUpdateImage() const { return true; } /** Returns the album cover image. * Returns a default image if no specific album image could be found. * In such a case it will start the cover fetcher. * * @param size is the maximum width or height of the resulting image. * when size is <= 1, return the full size image */ virtual QImage image( int size = 0 ) const; - virtual KUrl imageLocation( int size = 0 ); + virtual QUrl imageLocation( int size = 0 ); virtual void setImage( const QImage &image ); virtual void removeImage(); virtual void setSuppressImageAutoFetch( const bool suppress ) { m_suppressAutoFetch = suppress; } virtual bool suppressImageAutoFetch() const { return m_suppressAutoFetch; } virtual bool hasCapabilityInterface( Capabilities::Capability::Type type ) const; virtual Capabilities::Capability* createCapabilityInterface( Capabilities::Capability::Type type ); //SQL specific methods int id() const { return m_id; } Collections::SqlCollection *sqlCollection() const { return m_collection; } private: QByteArray md5sum( const QString& artist, const QString& album, const QString& file ) const; /** Returns a unique key for the album cover. */ QByteArray imageKey() const; /** Returns the path that the large scale image should have on the disk * Does not check if the file exists. * Note: not all large images have a disk cache, e.g. if they are set from outside * or embedded inside an audio file. * The largeDiskCache is only used for images set via setImage(QImage) */ QString largeDiskCachePath() const; /** Returns the path that the image should have on the disk * Does not check if the file exists. * @param size is the maximum width or height of the resulting image. * size==0 is the large image and the location of this file is completely different. * there should never be a scaled cached version of the large image. it dose not make * sense. */ QString scaledDiskCachePath( int size ) const; /** Returns the path to the large image * Queries the database for the path of the large scale image. */ QString largeImagePath(); /** Updates the database * Sets the current albums image to the given path. * The path should point to a valid image. * Note: setImage will not delete the already set image. */ void setImage( const QString &path ); /** Finds or creates a magic value in the database which tells Amarok not to auto fetch an image since it has been explicitly unset. */ int unsetImageId() const; private: Collections::SqlCollection* const m_collection; QString m_name; int m_id; // the id of this album in the database int m_artistId; int m_imageId; mutable QString m_imagePath; // path read from the database mutable bool m_hasImage; // true if we have an original image mutable bool m_hasImageChecked; // true if hasImage was checked mutable int m_unsetImageId; // this is the id of the unset magic value in the image sql database static const QString AMAROK_UNSET_MAGIC; bool m_tracksLoaded; bool m_suppressAutoFetch; Meta::ArtistPtr m_artist; Meta::TrackList m_tracks; mutable QMutex m_mutex; //TODO: add album artist friend class ::SqlRegistry; // needs to call notifyObservers friend class Meta::SqlTrack; // needs to set images directly friend class ::SqlScanResultProcessor; // needs to set images directly }; class AMAROK_SQLCOLLECTION_EXPORT SqlComposer : public Meta::Composer { public: SqlComposer( Collections::SqlCollection* collection, int id, const QString &name ); virtual QString name() const { return m_name; } virtual void invalidateCache(); virtual Meta::TrackList tracks(); //SQL specific methods int id() const { return m_id; } private: Collections::SqlCollection* const m_collection; const int m_id; const QString m_name; bool m_tracksLoaded; Meta::TrackList m_tracks; QMutex m_mutex; friend class ::SqlRegistry; // needs to call notifyObservers friend class Meta::SqlTrack; // needs to call notifyObservers }; class SqlGenre : public Meta::Genre { public: SqlGenre( Collections::SqlCollection* collection, int id, const QString &name ); virtual QString name() const { return m_name; } /** Invalidates the tracks cache */ /** Invalidates the tracks cache */ virtual void invalidateCache(); virtual Meta::TrackList tracks(); //SQL specific methods int id() const { return m_id; } private: Collections::SqlCollection* const m_collection; const int m_id; const QString m_name; bool m_tracksLoaded; Meta::TrackList m_tracks; QMutex m_mutex; friend class ::SqlRegistry; // needs to call notifyObservers friend class Meta::SqlTrack; // needs to call notifyObservers }; class AMAROK_SQLCOLLECTION_EXPORT SqlYear : public Meta::Year { public: SqlYear( Collections::SqlCollection* collection, int id, int year ); virtual QString name() const { return QString::number(m_year); } virtual int year() const { return m_year; } /** Invalidates the tracks cache */ virtual void invalidateCache(); virtual Meta::TrackList tracks(); //SQL specific methods int id() const { return m_id; } private: Collections::SqlCollection* const m_collection; const int m_id; const int m_year; bool m_tracksLoaded; Meta::TrackList m_tracks; QMutex m_mutex; friend class ::SqlRegistry; // needs to call notifyObservers friend class Meta::SqlTrack; // needs to call notifyObservers }; class AMAROK_SQLCOLLECTION_EXPORT SqlLabel : public Meta::Label { public: SqlLabel( Collections::SqlCollection *collection, int id, const QString &name ); virtual QString name() const { return m_name; } /** Invalidates the tracks cache */ virtual void invalidateCache(); virtual Meta::TrackList tracks(); //SQL specific methods int id() const { return m_id; } private: Collections::SqlCollection* const m_collection; const int m_id; const QString m_name; bool m_tracksLoaded; Meta::TrackList m_tracks; QMutex m_mutex; friend class ::SqlRegistry; // needs to call notifyObservers friend class Meta::SqlTrack; // needs to call notifyObservers }; typedef KSharedPtr SqlTrackPtr; typedef KSharedPtr SqlArtistPtr; typedef KSharedPtr SqlAlbumPtr; typedef KSharedPtr SqlComposerPtr; typedef KSharedPtr SqlGenrePtr; typedef KSharedPtr SqlYearPtr; } #endif /* SQLMETA_H */ diff --git a/src/core-impl/collections/db/sql/SqlQueryMaker.cpp b/src/core-impl/collections/db/sql/SqlQueryMaker.cpp index 13624d70a5..b4427f2db1 100644 --- a/src/core-impl/collections/db/sql/SqlQueryMaker.cpp +++ b/src/core-impl/collections/db/sql/SqlQueryMaker.cpp @@ -1,1156 +1,1156 @@ /**************************************************************************************** * Copyright (c) 2007 Maximilian Kossick * * Copyright (c) 2008 Daniel Winter * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "SqlQueryMaker" #include "SqlQueryMaker.h" #include "SqlCollection.h" #include "SqlQueryMakerInternal.h" #include #include "core/support/Debug.h" #include "core-impl/collections/db/MountPointManager.h" #include #include #include #include using namespace Collections; class SqlWorkerThread : public ThreadWeaver::Job { public: SqlWorkerThread( SqlQueryMakerInternal *queryMakerInternal ) : ThreadWeaver::Job() , m_queryMakerInternal( queryMakerInternal ) , m_aborted( false ) { //nothing to do } virtual ~SqlWorkerThread() { delete m_queryMakerInternal; } virtual void requestAbort() { m_aborted = true; } SqlQueryMakerInternal* queryMakerInternal() const { return m_queryMakerInternal; } protected: virtual void run() { m_queryMakerInternal->run(); setFinished( !m_aborted ); } private: SqlQueryMakerInternal *m_queryMakerInternal; bool m_aborted; }; struct SqlQueryMaker::Private { enum { TAGS_TAB = 1, ARTIST_TAB = 2, ALBUM_TAB = 4, GENRE_TAB = 8, COMPOSER_TAB = 16, YEAR_TAB = 32, STATISTICS_TAB = 64, URLS_TAB = 128, ALBUMARTIST_TAB = 256, LABELS_TAB = 1024 }; int linkedTables; QueryMaker::QueryType queryType; QString query; QString queryReturnValues; QString queryFrom; QString queryMatch; QString queryFilter; QString queryOrderBy; bool withoutDuplicates; int maxResultSize; AlbumQueryMode albumMode; LabelQueryMode labelMode; SqlWorkerThread *worker; QStack andStack; QStringList blockingCustomData; Meta::TrackList blockingTracks; Meta::AlbumList blockingAlbums; Meta::ArtistList blockingArtists; Meta::GenreList blockingGenres; Meta::ComposerList blockingComposers; Meta::YearList blockingYears; Meta::LabelList blockingLabels; bool blocking; bool used; qint64 returnValueType; }; SqlQueryMaker::SqlQueryMaker( SqlCollection* collection ) : QueryMaker() , m_collection( collection ) , d( new Private ) { d->worker = 0; d->queryType = QueryMaker::None; d->linkedTables = 0; d->withoutDuplicates = false; d->albumMode = AllAlbums; d->labelMode = QueryMaker::NoConstraint; d->maxResultSize = -1; d->andStack.clear(); d->andStack.push( true ); //and is default d->blocking = false; d->used = false; d->returnValueType = 0; } SqlQueryMaker::~SqlQueryMaker() { disconnect(); abortQuery(); if( d->worker ) d->worker->deleteLater(); delete d; } void SqlQueryMaker::abortQuery() { if( d->worker ) { d->worker->requestAbort(); d->worker->disconnect( this ); if( d->worker->queryMakerInternal() ) d->worker->queryMakerInternal()->disconnect( this ); } } void SqlQueryMaker::run() { if( d->queryType == QueryMaker::None || (d->blocking && d->used) ) { debug() << "sql querymaker used without reset or initialization" << endl; return; //better error handling? } if( d->worker && !d->worker->isFinished() ) { //the worker thread seems to be running //TODO: wait or job to complete } else { SqlQueryMakerInternal *qmi = new SqlQueryMakerInternal( m_collection ); qmi->setQuery( query() ); qmi->setQueryType( d->queryType ); if ( !d->blocking ) { connect( qmi, SIGNAL(newResultReady(Meta::AlbumList)), SIGNAL(newResultReady(Meta::AlbumList)), Qt::DirectConnection ); connect( qmi, SIGNAL(newResultReady(Meta::ArtistList)), SIGNAL(newResultReady(Meta::ArtistList)), Qt::DirectConnection ); connect( qmi, SIGNAL(newResultReady(Meta::GenreList)), SIGNAL(newResultReady(Meta::GenreList)), Qt::DirectConnection ); connect( qmi, SIGNAL(newResultReady(Meta::ComposerList)), SIGNAL(newResultReady(Meta::ComposerList)), Qt::DirectConnection ); connect( qmi, SIGNAL(newResultReady(Meta::YearList)), SIGNAL(newResultReady(Meta::YearList)), Qt::DirectConnection ); connect( qmi, SIGNAL(newResultReady(Meta::TrackList)), SIGNAL(newResultReady(Meta::TrackList)), Qt::DirectConnection ); connect( qmi, SIGNAL(newResultReady(QStringList)), SIGNAL(newResultReady(QStringList)), Qt::DirectConnection ); connect( qmi, SIGNAL(newResultReady(Meta::LabelList)), SIGNAL(newResultReady(Meta::LabelList)), Qt::DirectConnection ); d->worker = new SqlWorkerThread( qmi ); connect( d->worker, SIGNAL(done(ThreadWeaver::Job*)), SLOT(done(ThreadWeaver::Job*)) ); ThreadWeaver::Weaver::instance()->enqueue( d->worker ); } else //use it blocking { connect( qmi, SIGNAL(newResultReady(Meta::AlbumList)), SLOT(blockingNewResultReady(Meta::AlbumList)), Qt::DirectConnection ); connect( qmi, SIGNAL(newResultReady(Meta::ArtistList)), SLOT(blockingNewResultReady(Meta::ArtistList)), Qt::DirectConnection ); connect( qmi, SIGNAL(newResultReady(Meta::GenreList)), SLOT(blockingNewResultReady(Meta::GenreList)), Qt::DirectConnection ); connect( qmi, SIGNAL(newResultReady(Meta::ComposerList)), SLOT(blockingNewResultReady(Meta::ComposerList)), Qt::DirectConnection ); connect( qmi, SIGNAL(newResultReady(Meta::YearList)), SLOT(blockingNewResultReady(Meta::YearList)), Qt::DirectConnection ); connect( qmi, SIGNAL(newResultReady(Meta::TrackList)), SLOT(blockingNewResultReady(Meta::TrackList)), Qt::DirectConnection ); connect( qmi, SIGNAL(newResultReady(QStringList)), SLOT(blockingNewResultReady(QStringList)), Qt::DirectConnection ); connect( qmi, SIGNAL(newResultReady(Meta::LabelList)), SLOT(blockingNewResultReady(Meta::LabelList)), Qt::DirectConnection ); qmi->run(); delete qmi; } } d->used = true; } void SqlQueryMaker::done( ThreadWeaver::Job *job ) { job->deleteLater(); d->worker = 0; // d->worker *is* the job, prevent stale pointer emit queryDone(); } QueryMaker* SqlQueryMaker::setQueryType( QueryType type ) { // we need the unchanged m_queryType in the blocking result methods so prevent // reseting queryType without reseting the QM if ( d->blocking && d->used ) return this; switch( type ) { case QueryMaker::Track: //make sure to keep this method in sync with handleTracks(QStringList) and the SqlTrack ctor if( d->queryType == QueryMaker::None ) { d->queryType = QueryMaker::Track; d->linkedTables |= Private::URLS_TAB; d->linkedTables |= Private::TAGS_TAB; d->linkedTables |= Private::GENRE_TAB; d->linkedTables |= Private::ARTIST_TAB; d->linkedTables |= Private::ALBUM_TAB; d->linkedTables |= Private::COMPOSER_TAB; d->linkedTables |= Private::YEAR_TAB; d->linkedTables |= Private::STATISTICS_TAB; d->queryReturnValues = Meta::SqlTrack::getTrackReturnValues(); } return this; case QueryMaker::Artist: if( d->queryType == QueryMaker::None ) { d->queryType = QueryMaker::Artist; d->withoutDuplicates = true; d->linkedTables |= Private::ARTIST_TAB; //reading the ids from the database means we don't have to query for them later d->queryReturnValues = "artists.name, artists.id"; } return this; case QueryMaker::Album: if( d->queryType == QueryMaker::None ) { d->queryType = QueryMaker::Album; d->withoutDuplicates = true; d->linkedTables |= Private::ALBUM_TAB; //add whatever is necessary to identify compilations d->queryReturnValues = "albums.name, albums.id, albums.artist"; } return this; case QueryMaker::AlbumArtist: if( d->queryType == QueryMaker::None ) { d->queryType = QueryMaker::AlbumArtist; d->withoutDuplicates = true; d->linkedTables |= Private::ALBUMARTIST_TAB; d->linkedTables |= Private::ALBUM_TAB; d->queryReturnValues = "albumartists.name, albumartists.id"; } return this; case QueryMaker::Composer: if( d->queryType == QueryMaker::None ) { d->queryType = QueryMaker::Composer; d->withoutDuplicates = true; d->linkedTables |= Private::COMPOSER_TAB; d->queryReturnValues = "composers.name, composers.id"; } return this; case QueryMaker::Genre: if( d->queryType == QueryMaker::None ) { d->queryType = QueryMaker::Genre; d->withoutDuplicates = true; d->linkedTables |= Private::GENRE_TAB; d->queryReturnValues = "genres.name, genres.id"; } return this; case QueryMaker::Year: if( d->queryType == QueryMaker::None ) { d->queryType = QueryMaker::Year; d->withoutDuplicates = true; d->linkedTables |= Private::YEAR_TAB; d->queryReturnValues = "years.name, years.id"; } return this; case QueryMaker::Custom: if( d->queryType == QueryMaker::None ) d->queryType = QueryMaker::Custom; return this; case QueryMaker::Label: if( d->queryType == QueryMaker::None ) { d->queryType = QueryMaker::Label; d->withoutDuplicates = true; d->queryReturnValues = "labels.label,labels.id"; d->linkedTables |= Private::LABELS_TAB; } case QueryMaker::None: return this; } return this; } QueryMaker* SqlQueryMaker::addMatch( const Meta::TrackPtr &track ) { QString url = track->uidUrl(); if( !url.isEmpty() ) /* - KUrl kurl( url ); - if( kurl.protocol() == "amarok-sqltrackuid" ) + QUrl kurl( url ); + if( kurl.scheme() == "amarok-sqltrackuid" ) */ { d->queryMatch += QString( " AND urls.uniqueid = '%1' " ).arg( url /*kurl.url()*/ ); } else { QString path; /* if( kurl.isLocalFile() ) { path = kurl.path(); } else */ { path = track->playableUrl().path(); } int deviceid = m_collection->mountPointManager()->getIdForUrl( path ); QString rpath = m_collection->mountPointManager()->getRelativePath( deviceid, path ); d->queryMatch += QString( " AND urls.deviceid = %1 AND urls.rpath = '%2'" ) .arg( QString::number( deviceid ), escape( rpath ) ); } return this; } QueryMaker* SqlQueryMaker::addMatch( const Meta::ArtistPtr &artist, ArtistMatchBehaviour behaviour ) { d->linkedTables |= Private::ARTIST_TAB; if( behaviour == AlbumArtists || behaviour == AlbumOrTrackArtists ) d->linkedTables |= Private::ALBUMARTIST_TAB; QString artistQuery; QString albumArtistQuery; if( artist && !artist->name().isEmpty() ) { artistQuery = QString("artists.name = '%1'").arg( escape( artist->name() ) ); albumArtistQuery = QString("albumartists.name = '%1'").arg( escape( artist->name() ) ); } else { artistQuery = "( artists.name IS NULL OR artists.name = '')"; albumArtistQuery = "( albumartists.name IS NULL OR albumartists.name = '')"; } switch( behaviour ) { case TrackArtists: d->queryMatch += " AND " + artistQuery; break; case AlbumArtists: d->queryMatch += " AND " + albumArtistQuery; break; case AlbumOrTrackArtists: d->queryMatch += " AND ( (" + artistQuery + " ) OR ( " + albumArtistQuery + " ) )"; break; } return this; } QueryMaker* SqlQueryMaker::addMatch( const Meta::AlbumPtr &album ) { d->linkedTables |= Private::ALBUM_TAB; // handle singles if( !album || album->name().isEmpty() ) d->queryMatch += QString( " AND ( albums.name IS NULL OR albums.name = '' )" ); else d->queryMatch += QString( " AND albums.name = '%1'" ).arg( escape( album->name() ) ); if( album ) { //handle compilations Meta::ArtistPtr albumArtist = album->albumArtist(); if( albumArtist ) { d->linkedTables |= Private::ALBUMARTIST_TAB; d->queryMatch += QString( " AND albumartists.name = '%1'" ).arg( escape( albumArtist->name() ) ); } else { d->queryMatch += " AND albums.artist IS NULL"; } } return this; } QueryMaker* SqlQueryMaker::addMatch( const Meta::GenrePtr &genre ) { d->linkedTables |= Private::GENRE_TAB; d->queryMatch += QString( " AND genres.name = '%1'" ).arg( escape( genre->name() ) ); return this; } QueryMaker* SqlQueryMaker::addMatch( const Meta::ComposerPtr &composer ) { d->linkedTables |= Private::COMPOSER_TAB; d->queryMatch += QString( " AND composers.name = '%1'" ).arg( escape( composer->name() ) ); return this; } QueryMaker* SqlQueryMaker::addMatch( const Meta::YearPtr &year ) { // handle tracks without a year if( !year ) { d->queryMatch += " AND year IS NULL"; } else { d->linkedTables |= Private::YEAR_TAB; d->queryMatch += QString( " AND years.name = '%1'" ).arg( escape( year->name() ) ); } return this; } QueryMaker* SqlQueryMaker::addMatch( const Meta::LabelPtr &label ) { KSharedPtr sqlLabel = KSharedPtr::dynamicCast( label ); QString labelSubQuery; if( sqlLabel ) { labelSubQuery = "SELECT url FROM urls_labels WHERE label = %1"; labelSubQuery = labelSubQuery.arg( sqlLabel->id() ); } else { labelSubQuery = "SELECT a.url FROM urls_labels a INNER JOIN labels b ON a.label = b.id WHERE b.label = '%1'"; labelSubQuery = labelSubQuery.arg( escape( label->name() ) ); } d->linkedTables |= Private::TAGS_TAB; QString match = " AND tracks.url in (%1)"; d->queryMatch += match.arg( labelSubQuery ); return this; } QueryMaker* SqlQueryMaker::addFilter( qint64 value, const QString &filter, bool matchBegin, bool matchEnd ) { // special case for albumartist... if( value == Meta::valAlbumArtist && filter.isEmpty() ) { d->linkedTables |= Private::ALBUMARTIST_TAB; d->linkedTables |= Private::ALBUM_TAB; d->queryFilter += QString( " %1 ( albums.artist IS NULL or albumartists.name = '') " ).arg( andOr() ); } else if( value == Meta::valLabel ) { d->linkedTables |= Private::TAGS_TAB; QString like = likeCondition( filter, !matchBegin, !matchEnd ); QString filter = " %1 tracks.url IN (SELECT a.url FROM urls_labels a INNER JOIN labels b ON a.label = b.id WHERE b.label %2) "; d->queryFilter += filter.arg( andOr(), like ); } else { QString like = likeCondition( filter, !matchBegin, !matchEnd ); d->queryFilter += QString( " %1 %2 %3 " ).arg( andOr(), nameForValue( value ), like ); } return this; } QueryMaker* SqlQueryMaker::excludeFilter( qint64 value, const QString &filter, bool matchBegin, bool matchEnd ) { // special case for album... if( value == Meta::valAlbumArtist && filter.isEmpty() ) { d->linkedTables |= Private::ALBUMARTIST_TAB; d->queryFilter += QString( " %1 NOT ( albums.artist IS NULL or albumartists.name = '') " ).arg( andOr() ); } else if( value == Meta::valLabel ) { d->linkedTables |= Private::TAGS_TAB; QString like = likeCondition( filter, !matchBegin, !matchEnd ); QString filter = " %1 tracks.url NOT IN (SELECT a.url FROM urls_labels a INNER JOIN labels b ON a.label = b.id WHERE b.label %2) "; d->queryFilter += filter.arg( andOr(), like ); } else { QString like = likeCondition( filter, !matchBegin, !matchEnd ); d->queryFilter += QString( " %1 NOT %2 %3 " ).arg( andOr(), nameForValue( value ), like ); } return this; } QueryMaker* SqlQueryMaker::addNumberFilter( qint64 value, qint64 filter, QueryMaker::NumberComparison compare ) { QString comparison; switch( compare ) { case QueryMaker::Equals: comparison = '='; break; case QueryMaker::GreaterThan: comparison = '>'; break; case QueryMaker::LessThan: comparison = '<'; break; } // note: a NULL value in the database means undefined and not 0! d->queryFilter += QString( " %1 %2 %3 %4 " ).arg( andOr(), nameForValue( value ), comparison, QString::number( filter ) ); return this; } QueryMaker* SqlQueryMaker::excludeNumberFilter( qint64 value, qint64 filter, QueryMaker::NumberComparison compare ) { QString comparison; switch( compare ) { case QueryMaker::Equals: comparison = "!="; break; case QueryMaker::GreaterThan: //negating greater than is less or equal comparison = "<="; break; case QueryMaker::LessThan: //negating less than is greater or equal comparison = ">="; break; } // note: a NULL value in the database means undefined and not 0! // We can't exclude NULL values here because they are not defined! d->queryFilter += QString( " %1 (%2 %3 %4 or %2 is null)" ).arg( andOr(), nameForValue( value ), comparison, QString::number( filter ) ); return this; } QueryMaker* SqlQueryMaker::addReturnValue( qint64 value ) { if( d->queryType == QueryMaker::Custom ) { if ( !d->queryReturnValues.isEmpty() ) d->queryReturnValues += ','; d->queryReturnValues += nameForValue( value ); d->returnValueType = value; } return this; } QueryMaker* SqlQueryMaker::addReturnFunction( ReturnFunction function, qint64 value ) { if( d->queryType == QueryMaker::Custom ) { if( !d->queryReturnValues.isEmpty() ) d->queryReturnValues += ','; QString sqlfunction; switch( function ) { case QueryMaker::Count: sqlfunction = "COUNT"; break; case QueryMaker::Sum: sqlfunction = "SUM"; break; case QueryMaker::Max: sqlfunction = "MAX"; break; case QueryMaker::Min: sqlfunction = "MIN"; break; default: sqlfunction = "Unknown function in SqlQueryMaker::addReturnFunction, function was: " + QString::number( function ); } d->queryReturnValues += QString( "%1(%2)" ).arg( sqlfunction, nameForValue( value ) ); d->returnValueType = value; } return this; } QueryMaker* SqlQueryMaker::orderBy( qint64 value, bool descending ) { if ( d->queryOrderBy.isEmpty() ) d->queryOrderBy = " ORDER BY "; else d->queryOrderBy += ','; d->queryOrderBy += nameForValue( value ); d->queryOrderBy += QString( " %1 " ).arg( descending ? "DESC" : "ASC" ); return this; } QueryMaker* SqlQueryMaker::limitMaxResultSize( int size ) { d->maxResultSize = size; return this; } QueryMaker* SqlQueryMaker::setAlbumQueryMode( AlbumQueryMode mode ) { if( mode != AllAlbums ) { d->linkedTables |= Private::ALBUM_TAB; } d->albumMode = mode; return this; } QueryMaker* SqlQueryMaker::setLabelQueryMode( LabelQueryMode mode ) { d->labelMode = mode; return this; } QueryMaker* SqlQueryMaker::beginAnd() { d->queryFilter += andOr(); d->queryFilter += " ( 1 "; d->andStack.push( true ); return this; } QueryMaker* SqlQueryMaker::beginOr() { d->queryFilter += andOr(); d->queryFilter += " ( 0 "; d->andStack.push( false ); return this; } QueryMaker* SqlQueryMaker::endAndOr() { d->queryFilter += ')'; d->andStack.pop(); return this; } void SqlQueryMaker::linkTables() { switch( d->queryType ) { case QueryMaker::Track: { d->queryFrom += " tracks"; if( d->linkedTables & Private::TAGS_TAB ) d->linkedTables ^= Private::TAGS_TAB; break; } case QueryMaker::Artist: { d->queryFrom += " artists"; if( d->linkedTables != Private::ARTIST_TAB ) d->queryFrom += " JOIN tracks ON tracks.artist = artists.id"; if( d->linkedTables & Private::ARTIST_TAB ) d->linkedTables ^= Private::ARTIST_TAB; break; } case QueryMaker::Album: case QueryMaker::AlbumArtist: { d->queryFrom += " albums"; if( d->linkedTables != Private::ALBUM_TAB && d->linkedTables != ( Private::ALBUM_TAB | Private::ALBUMARTIST_TAB ) ) d->queryFrom += " JOIN tracks ON tracks.album = albums.id"; if( d->linkedTables & Private::ALBUM_TAB ) d->linkedTables ^= Private::ALBUM_TAB; break; } case QueryMaker::Genre: { d->queryFrom += " genres"; if( d->linkedTables != Private::GENRE_TAB ) d->queryFrom += " INNER JOIN tracks ON tracks.genre = genres.id"; if( d->linkedTables & Private::GENRE_TAB ) d->linkedTables ^= Private::GENRE_TAB; break; } case QueryMaker::Composer: { d->queryFrom += " composers"; if( d->linkedTables != Private::COMPOSER_TAB ) d->queryFrom += " JOIN tracks ON tracks.composer = composers.id"; if( d->linkedTables & Private::COMPOSER_TAB ) d->linkedTables ^= Private::COMPOSER_TAB; break; } case QueryMaker::Year: { d->queryFrom += " years"; if( d->linkedTables != Private::YEAR_TAB ) d->queryFrom += " JOIN tracks on tracks.year = years.id"; if( d->linkedTables & Private::YEAR_TAB ) d->linkedTables ^= Private::YEAR_TAB; break; } case QueryMaker::Label: { d->queryFrom += " labels"; if( d->linkedTables != Private::LABELS_TAB ) d->queryFrom += " INNER JOIN urls_labels ON labels.id = urls_labels.label" " INNER JOIN tracks ON urls_labels.url = tracks.url"; if( d->linkedTables & Private::LABELS_TAB ) d->linkedTables ^= Private::LABELS_TAB; break; } case QueryMaker::Custom: { switch( d->returnValueType ) { default: case Meta::valUrl: { d->queryFrom += " tracks"; if( d->linkedTables & Private::TAGS_TAB ) d->linkedTables ^= Private::TAGS_TAB; break; } case Meta::valAlbum: { d->queryFrom += " albums"; if( d->linkedTables & Private::ALBUM_TAB ) d->linkedTables ^= Private::ALBUM_TAB; if( d->linkedTables & Private::URLS_TAB ) d->linkedTables ^= Private::URLS_TAB; break; } case Meta::valArtist: { d->queryFrom += " artists"; if( d->linkedTables & Private::ARTIST_TAB ) d->linkedTables ^= Private::ARTIST_TAB; if( d->linkedTables & Private::URLS_TAB ) d->linkedTables ^= Private::URLS_TAB; break; } case Meta::valGenre: { d->queryFrom += " genres"; if( d->linkedTables & Private::GENRE_TAB ) d->linkedTables ^= Private::GENRE_TAB; if( d->linkedTables & Private::URLS_TAB ) d->linkedTables ^= Private::URLS_TAB; break; } } } case QueryMaker::None: { //??? break; } } if( !d->linkedTables ) return; if( d->linkedTables & Private::URLS_TAB ) d->queryFrom += " INNER JOIN urls ON tracks.url = urls.id"; if( d->linkedTables & Private::ARTIST_TAB ) d->queryFrom += " LEFT JOIN artists ON tracks.artist = artists.id"; if( d->linkedTables & Private::ALBUM_TAB ) d->queryFrom += " LEFT JOIN albums ON tracks.album = albums.id"; if( d->linkedTables & Private::ALBUMARTIST_TAB ) d->queryFrom += " LEFT JOIN artists AS albumartists ON albums.artist = albumartists.id"; if( d->linkedTables & Private::GENRE_TAB ) d->queryFrom += " LEFT JOIN genres ON tracks.genre = genres.id"; if( d->linkedTables & Private::COMPOSER_TAB ) d->queryFrom += " LEFT JOIN composers ON tracks.composer = composers.id"; if( d->linkedTables & Private::YEAR_TAB ) d->queryFrom += " LEFT JOIN years ON tracks.year = years.id"; if( d->linkedTables & Private::STATISTICS_TAB ) { if( d->linkedTables & Private::URLS_TAB ) { d->queryFrom += " LEFT JOIN statistics ON urls.id = statistics.url"; } else { d->queryFrom += " LEFT JOIN statistics ON tracks.url = statistics.url"; } } } void SqlQueryMaker::buildQuery() { //URLS is always required for dynamic collection d->linkedTables |= Private::URLS_TAB; linkTables(); QString query = "SELECT "; if ( d->withoutDuplicates ) query += "DISTINCT "; query += d->queryReturnValues; query += " FROM "; query += d->queryFrom; // dynamic collection (only mounted file systems are considered) if( (d->linkedTables & Private::URLS_TAB) && m_collection->mountPointManager() ) { query += " WHERE 1 "; IdList list = m_collection->mountPointManager()->getMountedDeviceIds(); if( !list.isEmpty() ) { QString commaSeparatedIds; foreach( int id, list ) { if( !commaSeparatedIds.isEmpty() ) commaSeparatedIds += ','; commaSeparatedIds += QString::number( id ); } query += QString( " AND urls.deviceid in (%1)" ).arg( commaSeparatedIds ); } } switch( d->albumMode ) { case OnlyNormalAlbums: query += " AND albums.artist IS NOT NULL "; break; case OnlyCompilations: query += " AND albums.artist IS NULL "; break; case AllAlbums: //do nothing break; } if( d->labelMode != QueryMaker::NoConstraint ) { switch( d->labelMode ) { case QueryMaker::OnlyWithLabels: query += " AND tracks.url IN "; break; case QueryMaker::OnlyWithoutLabels: query += " AND tracks.url NOT IN "; break; case QueryMaker::NoConstraint: //do nothing, will never be called break; } query += " (SELECT DISTINCT url FROM urls_labels) "; } query += d->queryMatch; if ( !d->queryFilter.isEmpty() ) { query += " AND ( 1 "; query += d->queryFilter; query += " ) "; } query += d->queryOrderBy; if ( d->maxResultSize > -1 ) query += QString( " LIMIT %1 OFFSET 0 " ).arg( d->maxResultSize ); query += ';'; d->query = query; } QString SqlQueryMaker::query() { if ( d->query.isEmpty() ) buildQuery(); return d->query; } QStringList SqlQueryMaker::runQuery( const QString &query ) { return m_collection->sqlStorage()->query( query ); } void SqlQueryMaker::setBlocking( bool enabled ) { d->blocking = enabled; } QStringList SqlQueryMaker::collectionIds() const { QStringList list; list << m_collection->collectionId(); return list; } Meta::TrackList SqlQueryMaker::tracks() const { return d->blockingTracks; } Meta::AlbumList SqlQueryMaker::albums() const { return d->blockingAlbums; } Meta::ArtistList SqlQueryMaker::artists() const { return d->blockingArtists; } Meta::GenreList SqlQueryMaker::genres() const { return d->blockingGenres; } Meta::ComposerList SqlQueryMaker::composers() const { return d->blockingComposers; } Meta::YearList SqlQueryMaker::years() const { return d->blockingYears; } QStringList SqlQueryMaker::customData() const { return d->blockingCustomData; } Meta::LabelList SqlQueryMaker::labels() const { return d->blockingLabels; } QString SqlQueryMaker::nameForValue( qint64 value ) { switch( value ) { case Meta::valUrl: d->linkedTables |= Private::URLS_TAB; return "urls.rpath"; //TODO figure out how to handle deviceid case Meta::valTitle: d->linkedTables |= Private::TAGS_TAB; return "tracks.title"; case Meta::valArtist: d->linkedTables |= Private::ARTIST_TAB; return "artists.name"; case Meta::valAlbum: d->linkedTables |= Private::ALBUM_TAB; return "albums.name"; case Meta::valGenre: d->linkedTables |= Private::GENRE_TAB; return "genres.name"; case Meta::valComposer: d->linkedTables |= Private::COMPOSER_TAB; return "composers.name"; case Meta::valYear: d->linkedTables |= Private::YEAR_TAB; return "years.name"; case Meta::valBpm: d->linkedTables |= Private::TAGS_TAB; return "tracks.bpm"; case Meta::valComment: d->linkedTables |= Private::TAGS_TAB; return "tracks.comment"; case Meta::valTrackNr: d->linkedTables |= Private::TAGS_TAB; return "tracks.tracknumber"; case Meta::valDiscNr: d->linkedTables |= Private::TAGS_TAB; return "tracks.discnumber"; case Meta::valLength: d->linkedTables |= Private::TAGS_TAB; return "tracks.length"; case Meta::valBitrate: d->linkedTables |= Private::TAGS_TAB; return "tracks.bitrate"; case Meta::valSamplerate: d->linkedTables |= Private::TAGS_TAB; return "tracks.samplerate"; case Meta::valFilesize: d->linkedTables |= Private::TAGS_TAB; return "tracks.filesize"; case Meta::valFormat: d->linkedTables |= Private::TAGS_TAB; return "tracks.filetype"; case Meta::valCreateDate: d->linkedTables |= Private::TAGS_TAB; return "tracks.createdate"; case Meta::valScore: d->linkedTables |= Private::STATISTICS_TAB; return "statistics.score"; case Meta::valRating: d->linkedTables |= Private::STATISTICS_TAB; return "statistics.rating"; case Meta::valFirstPlayed: d->linkedTables |= Private::STATISTICS_TAB; return "statistics.createdate"; case Meta::valLastPlayed: d->linkedTables |= Private::STATISTICS_TAB; return "statistics.accessdate"; case Meta::valPlaycount: d->linkedTables |= Private::STATISTICS_TAB; return "statistics.playcount"; case Meta::valUniqueId: d->linkedTables |= Private::URLS_TAB; return "urls.uniqueid"; case Meta::valAlbumArtist: d->linkedTables |= Private::ALBUMARTIST_TAB; //albumartist_tab means that the artist table is joined to the albums table //so add albums as well d->linkedTables |= Private::ALBUM_TAB; return "albumartists.name"; case Meta::valModified: d->linkedTables |= Private::TAGS_TAB; return "tracks.modifydate"; default: return "ERROR: unknown value in SqlQueryMaker::nameForValue(qint64): value=" + QString::number( value ); } } QString SqlQueryMaker::andOr() const { return d->andStack.top() ? " AND " : " OR "; } QString SqlQueryMaker::escape( QString text ) const //krazy:exclude=constref { return m_collection->sqlStorage()->escape( text ); } QString SqlQueryMaker::likeCondition( const QString &text, bool anyBegin, bool anyEnd ) const { if( anyBegin || anyEnd ) { QString escaped = text; //according to http://dev.mysql.com/doc/refman/5.0/en/string-comparison-functions.html //the escape character (\ as we are using the default) is escaped twice when using like. //mysql_real_escape will escape it once, so we have to escape it another time here escaped = escaped.replace( '\\', "\\\\" ); // "////" will result in two backslahes escaped = escape( escaped ); //as we are in pattern matching mode '_' and '%' have to be escaped //mysql_real_excape_string does not do that for us //see http://dev.mysql.com/doc/refman/5.0/en/string-syntax.html //and http://dev.mysql.com/doc/refman/5.0/en/mysql-real-escape-string.html //replace those characters after calling escape(), which calls the mysql //function in turn, so that mysql does not escape the escape backslashes escaped.replace( '%', "\\%" ).replace( '_', "\\_" ); QString ret = " LIKE "; ret += '\''; if ( anyBegin ) ret += '%'; ret += escaped; if ( anyEnd ) ret += '%'; ret += '\''; //Case insensitive collation for queries ret += " COLLATE utf8_unicode_ci "; //Use \ as the escape character //ret += " ESCAPE '\\' "; return ret; } else { return QString( " = '%1' COLLATE utf8_unicode_ci " ).arg( escape( text ) ); } } void SqlQueryMaker::blockingNewResultReady(const Meta::AlbumList &albums) { d->blockingAlbums = albums; } void SqlQueryMaker::blockingNewResultReady(const Meta::ArtistList &artists) { d->blockingArtists = artists; } void SqlQueryMaker::blockingNewResultReady(const Meta::GenreList &genres) { d->blockingGenres = genres; } void SqlQueryMaker::blockingNewResultReady(const Meta::ComposerList &composers) { d->blockingComposers = composers; } void SqlQueryMaker::blockingNewResultReady(const Meta::YearList &years) { d->blockingYears = years; } void SqlQueryMaker::blockingNewResultReady(const Meta::TrackList &tracks) { d->blockingTracks = tracks; } void SqlQueryMaker::blockingNewResultReady(const QStringList &customData) { d->blockingCustomData = customData; } void SqlQueryMaker::blockingNewResultReady(const Meta::LabelList &labels ) { d->blockingLabels = labels; } diff --git a/src/core-impl/collections/db/sql/device/massstorage/MassStorageDeviceHandler.cpp b/src/core-impl/collections/db/sql/device/massstorage/MassStorageDeviceHandler.cpp index c7f1d3d575..3aa059342b 100644 --- a/src/core-impl/collections/db/sql/device/massstorage/MassStorageDeviceHandler.cpp +++ b/src/core-impl/collections/db/sql/device/massstorage/MassStorageDeviceHandler.cpp @@ -1,189 +1,190 @@ /**************************************************************************************** * Copyright (c) 2006-2007 Maximilian Kossick * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "MassStorageDeviceHandler" #include "MassStorageDeviceHandler.h" #include "core/support/Debug.h" #include -#include +#include #include #include #include MassStorageDeviceHandler::MassStorageDeviceHandler(): DeviceHandler() { } MassStorageDeviceHandler::MassStorageDeviceHandler( int deviceId, const QString &mountPoint, const QString &udi ) : DeviceHandler() , m_deviceID( deviceId ) , m_mountPoint( mountPoint ) , m_udi( udi ) { DEBUG_BLOCK } MassStorageDeviceHandler::~MassStorageDeviceHandler() { } bool MassStorageDeviceHandler::isAvailable() const { return true; } QString MassStorageDeviceHandler::type() const { return "uuid"; } int MassStorageDeviceHandler::getDeviceID() { return m_deviceID; } const QString &MassStorageDeviceHandler::getDevicePath() const { return m_mountPoint; } -void MassStorageDeviceHandler::getURL( KUrl &absolutePath, const KUrl &relativePath ) +void MassStorageDeviceHandler::getURL( QUrl &absolutePath, const QUrl &relativePath ) { absolutePath.setPath( m_mountPoint ); - absolutePath.addPath( relativePath.path() ); + absolutePath = absolutePath.adjusted(QUrl::StripTrailingSlash); + absolutePath.setPath(absolutePath.path() + '/' + ( relativePath.path() )); absolutePath.cleanPath(); } -void MassStorageDeviceHandler::getPlayableURL( KUrl &absolutePath, const KUrl &relativePath ) +void MassStorageDeviceHandler::getPlayableURL( QUrl &absolutePath, const QUrl &relativePath ) { getURL( absolutePath, relativePath ); } bool MassStorageDeviceHandler::deviceMatchesUdi( const QString &udi ) const { return m_udi == udi; } /////////////////////////////////////////////////////////////////////////////// // class MassStorageDeviceHandlerFactory /////////////////////////////////////////////////////////////////////////////// QString MassStorageDeviceHandlerFactory::type( ) const { return "uuid"; } bool MassStorageDeviceHandlerFactory::canCreateFromMedium( ) const { return true; } bool MassStorageDeviceHandlerFactory::canCreateFromConfig( ) const { return false; } bool MassStorageDeviceHandlerFactory::canHandle( const Solid::Device &device ) const { DEBUG_BLOCK const Solid::StorageVolume *volume = device.as(); if( !volume ) { debug() << "found no volume"; return false; } if( volume->uuid().isEmpty() ) debug() << "has empty uuid"; if( volume->isIgnored() ) debug() << "volume is ignored"; if( excludedFilesystem( volume->fsType() ) ) debug() << "excluded filesystem of type " << volume->fsType(); return volume && !volume->uuid().isEmpty() && !volume->isIgnored() && !excludedFilesystem( volume->fsType() ); } MassStorageDeviceHandlerFactory::~MassStorageDeviceHandlerFactory( ) { } DeviceHandler * MassStorageDeviceHandlerFactory::createHandler( KSharedConfigPtr, SqlStorage* ) const { return 0; } DeviceHandler * MassStorageDeviceHandlerFactory::createHandler( const Solid::Device &device, const QString &udi, SqlStorage *s ) const { DEBUG_BLOCK if( !s ) { debug() << "!s, returning 0"; return 0; } const Solid::StorageVolume *volume = device.as(); const Solid::StorageAccess *volumeAccess = device.as(); if( !volume || !volumeAccess ) { debug() << "Volume isn't valid, can't create a handler"; return 0; } if( volumeAccess->filePath().isEmpty() ) { debug() << "not mounted, can't do anything"; return 0; // It's not mounted, we can't do anything. } QStringList ids = s->query( QString( "SELECT id, label, lastmountpoint " "FROM devices WHERE type = 'uuid' " "AND uuid = '%1';" ).arg( volume->uuid() ) ); if ( ids.size() == 3 ) { debug() << "Found existing UUID config for ID " << ids[0] << " , uuid " << volume->uuid(); s->query( QString( "UPDATE devices SET lastmountpoint = '%2' WHERE " "id = %1;" ) .arg( ids[0] ) .arg( s->escape( volumeAccess->filePath() ) ) ); return new MassStorageDeviceHandler( ids[0].toInt(), volumeAccess->filePath(), udi ); } else { const int id = s->insert( QString( "INSERT INTO devices( type, uuid, lastmountpoint ) " "VALUES ( 'uuid', '%1', '%2' );" ) .arg( volume->uuid() ) .arg( s->escape( volumeAccess->filePath() ) ), "devices" ); if ( id == 0 ) { warning() << "Inserting into devices failed for type=uuid, uuid=" << volume->uuid(); return 0; } debug() << "Created new UUID device with ID " << id << " , uuid " << volume->uuid(); return new MassStorageDeviceHandler( id, volumeAccess->filePath(), udi ); } } bool MassStorageDeviceHandlerFactory::excludedFilesystem( const QString &fstype ) const { return fstype.isEmpty() || fstype.indexOf( "smb" ) != -1 || fstype.indexOf( "cifs" ) != -1 || fstype.indexOf( "nfs" ) != -1 || fstype == "udf" || fstype == "iso9660" ; } diff --git a/src/core-impl/collections/db/sql/device/massstorage/MassStorageDeviceHandler.h b/src/core-impl/collections/db/sql/device/massstorage/MassStorageDeviceHandler.h index 5320c3cda5..be6e8c037c 100644 --- a/src/core-impl/collections/db/sql/device/massstorage/MassStorageDeviceHandler.h +++ b/src/core-impl/collections/db/sql/device/massstorage/MassStorageDeviceHandler.h @@ -1,72 +1,72 @@ /**************************************************************************************** * Copyright (c) 2006-2007 Maximilian Kossick * * * * 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, see . * ****************************************************************************************/ #ifndef MASSSTORAGEDEVICEHANDLER_H #define MASSSTORAGEDEVICEHANDLER_H #include "core-impl/collections/db/MountPointManager.h" class SqlStorage; class MassStorageDeviceHandlerFactory : public DeviceHandlerFactory { public: MassStorageDeviceHandlerFactory( QObject *parent ) : DeviceHandlerFactory( parent ) {} virtual ~MassStorageDeviceHandlerFactory(); virtual bool canHandle( const Solid::Device &device ) const; virtual bool canCreateFromMedium() const; virtual DeviceHandler* createHandler( const Solid::Device &device, const QString &uuid, SqlStorage *s ) const; virtual bool canCreateFromConfig() const; virtual DeviceHandler* createHandler( KSharedConfigPtr c, SqlStorage *s ) const; virtual QString type() const; private: bool excludedFilesystem( const QString &fstype ) const; }; /** @author Maximilian Kossick */ class MassStorageDeviceHandler : public DeviceHandler { public: MassStorageDeviceHandler(); MassStorageDeviceHandler(int deviceId, const QString &mountPoint, const QString &uuid ); virtual ~MassStorageDeviceHandler(); virtual bool isAvailable() const; virtual QString type() const; virtual int getDeviceID( ); virtual const QString &getDevicePath() const; - virtual void getURL( KUrl &absolutePath, const KUrl &relativePath ); - virtual void getPlayableURL( KUrl &absolutePath, const KUrl &relativePath ); + virtual void getURL( QUrl &absolutePath, const QUrl &relativePath ); + virtual void getPlayableURL( QUrl &absolutePath, const QUrl &relativePath ); virtual bool deviceMatchesUdi( const QString &udi ) const; private: int m_deviceID; const QString m_mountPoint; QString m_udi; }; #endif diff --git a/src/core-impl/collections/db/sql/device/nfs/NfsDeviceHandler.cpp b/src/core-impl/collections/db/sql/device/nfs/NfsDeviceHandler.cpp index cc7ba51955..5c54830cc5 100644 --- a/src/core-impl/collections/db/sql/device/nfs/NfsDeviceHandler.cpp +++ b/src/core-impl/collections/db/sql/device/nfs/NfsDeviceHandler.cpp @@ -1,206 +1,207 @@ /**************************************************************************************** * Copyright (c) 2006-2007 Maximilian Kossick * * Copyright (c) 2011 Peter C. Ndikuwera * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "NfsDeviceHandler" #include "NfsDeviceHandler.h" #include "core/support/Debug.h" #include -#include +#include #include #include #include NfsDeviceHandler::NfsDeviceHandler( int deviceId, const QString &server, const QString &share, const QString &mountPoint, const QString &udi ) : DeviceHandler() , m_deviceID( deviceId ) , m_server( server ) , m_share( share ) , m_mountPoint( mountPoint ) , m_udi( udi ) { DEBUG_BLOCK } NfsDeviceHandler::NfsDeviceHandler( int deviceId, const QString &mountPoint, const QString &udi ) : DeviceHandler() , m_deviceID( deviceId ) , m_mountPoint( mountPoint ) , m_udi( udi ) { DEBUG_BLOCK } NfsDeviceHandler::~NfsDeviceHandler() { } bool NfsDeviceHandler::isAvailable() const { return true; } QString NfsDeviceHandler::type() const { return "nfs"; } int NfsDeviceHandler::getDeviceID() { return m_deviceID; } const QString &NfsDeviceHandler::getDevicePath() const { return m_mountPoint; } -void NfsDeviceHandler::getURL( KUrl &absolutePath, const KUrl &relativePath ) +void NfsDeviceHandler::getURL( QUrl &absolutePath, const QUrl &relativePath ) { absolutePath.setPath( m_mountPoint ); - absolutePath.addPath( relativePath.path() ); + absolutePath = absolutePath.adjusted(QUrl::StripTrailingSlash); + absolutePath.setPath(absolutePath.path() + '/' + ( relativePath.path() )); absolutePath.cleanPath(); } -void NfsDeviceHandler::getPlayableURL( KUrl &absolutePath, const KUrl &relativePath ) +void NfsDeviceHandler::getPlayableURL( QUrl &absolutePath, const QUrl &relativePath ) { getURL( absolutePath, relativePath ); } bool NfsDeviceHandler::deviceMatchesUdi( const QString &udi ) const { return m_udi == udi; } /////////////////////////////////////////////////////////////////////////////// // class NfsDeviceHandlerFactory /////////////////////////////////////////////////////////////////////////////// QString NfsDeviceHandlerFactory::type( ) const { return "nfs"; } bool NfsDeviceHandlerFactory::canCreateFromMedium( ) const { return true; } bool NfsDeviceHandlerFactory::canCreateFromConfig( ) const { return false; } bool NfsDeviceHandlerFactory::canHandle( const Solid::Device &device ) const { const Solid::NetworkShare *share = device.as(); if( !share ) { debug() << __PRETTY_FUNCTION__ << device.udi() << "has no NetworkShare interface"; return false; } if( share->type() != Solid::NetworkShare::Nfs ) { debug() << __PRETTY_FUNCTION__ << device.udi() << "has type" << share->type() << "but nfs type is" << Solid::NetworkShare::Nfs; return false; } const Solid::StorageAccess *access = device.as(); if( !access ) { debug() << __PRETTY_FUNCTION__ << device.udi() << "has no StorageAccess interface"; return false; } if( !access->isAccessible() || access->filePath().isEmpty() ) { debug() << __PRETTY_FUNCTION__ << device.udi() << "is not accessible" << "or has empty mount-point"; return false; } return true; } NfsDeviceHandlerFactory::~NfsDeviceHandlerFactory( ) { } DeviceHandler * NfsDeviceHandlerFactory::createHandler( KSharedConfigPtr, SqlStorage* ) const { return 0; } DeviceHandler * NfsDeviceHandlerFactory::createHandler( const Solid::Device &device, const QString &udi, SqlStorage *s ) const { DEBUG_BLOCK if( !s ) { debug() << "!s, returning 0"; return 0; } if( !canHandle( device ) ) return 0; const Solid::StorageAccess *access = device.as(); Q_ASSERT( access ); // canHandle() checks it QString mountPoint = access->filePath(); const Solid::NetworkShare *netShare = device.as(); Q_ASSERT( netShare ); // canHandle() checks it QUrl url = netShare->url(); // nfs://thinkpad/test or nfs://thinkpad/ QString server = url.host(); QString share = url.path(); // leading slash is preserved for nfs shares QStringList ids = s->query( QString( "SELECT id, label, lastmountpoint " "FROM devices WHERE type = 'nfs' " "AND servername = '%1' AND sharename = '%2';" ) .arg( s->escape( server ) ) .arg( s->escape( share ) ) ); if ( ids.size() == 3 ) { debug() << "Found existing NFS config for ID " << ids[0] << " , server " << server << " ,share " << share; s->query( QString( "UPDATE devices SET lastmountpoint = '%2' WHERE " "id = %1;" ) .arg( ids[0] ) .arg( s->escape( mountPoint ) ) ); return new NfsDeviceHandler( ids[0].toInt(), server, share, mountPoint, udi ); } else { int id = s->insert( QString( "INSERT INTO devices" "( type, servername, sharename, lastmountpoint ) " "VALUES ( 'nfs', '%1', '%2', '%3' );" ) .arg( s->escape( server ) ) .arg( s->escape( share ) ) .arg( s->escape( mountPoint ) ), "devices" ); if ( id == 0 ) { warning() << "Inserting into devices failed for type=nfs, server=" << server << ", share=" << share; return 0; } debug() << "Created new NFS device with ID " << id << " , server " << server << " ,share " << share; return new NfsDeviceHandler( id, server, share, mountPoint, udi ); } } diff --git a/src/core-impl/collections/db/sql/device/nfs/NfsDeviceHandler.h b/src/core-impl/collections/db/sql/device/nfs/NfsDeviceHandler.h index 702f8c1f5b..ff2bd6242a 100644 --- a/src/core-impl/collections/db/sql/device/nfs/NfsDeviceHandler.h +++ b/src/core-impl/collections/db/sql/device/nfs/NfsDeviceHandler.h @@ -1,72 +1,72 @@ /**************************************************************************************** * Copyright (c) 2006-2007 Maximilian Kossick * * Copyright (c) 2011 Peter C. Ndikuwera * * * * 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, see . * ****************************************************************************************/ #ifndef NFSDEVICEHANDLER_H #define NFSDEVICEHANDLER_H #include "core-impl/collections/db/MountPointManager.h" class NfsDeviceHandlerFactory : public DeviceHandlerFactory { public: NfsDeviceHandlerFactory( QObject *parent ) : DeviceHandlerFactory( parent ) {} virtual ~NfsDeviceHandlerFactory(); virtual bool canHandle( const Solid::Device &device ) const; virtual bool canCreateFromMedium() const; virtual DeviceHandler* createHandler( const Solid::Device &device, const QString &uuid, SqlStorage *s ) const; virtual bool canCreateFromConfig() const; virtual DeviceHandler* createHandler( KSharedConfigPtr c, SqlStorage *s ) const; virtual QString type() const; }; /** @author Maximilian Kossick */ class NfsDeviceHandler : public DeviceHandler { public: NfsDeviceHandler(); NfsDeviceHandler(int deviceId, const QString &mountPoint, const QString &udi ); NfsDeviceHandler(int deviceId, const QString &server, const QString &share, const QString &mountPoint, const QString &udi ); virtual ~NfsDeviceHandler(); virtual bool isAvailable() const; virtual QString type() const; virtual int getDeviceID( ); virtual const QString &getDevicePath() const; - virtual void getURL( KUrl &absolutePath, const KUrl &relativePath ); - virtual void getPlayableURL( KUrl &absolutePath, const KUrl &relativePath ); + virtual void getURL( QUrl &absolutePath, const QUrl &relativePath ); + virtual void getPlayableURL( QUrl &absolutePath, const QUrl &relativePath ); virtual bool deviceMatchesUdi( const QString &udi ) const; private: int m_deviceID; QString m_server; QString m_share; const QString m_mountPoint; QString m_udi; }; #endif diff --git a/src/core-impl/collections/db/sql/device/smb/SmbDeviceHandler.cpp b/src/core-impl/collections/db/sql/device/smb/SmbDeviceHandler.cpp index a6d8df2825..e779901af7 100644 --- a/src/core-impl/collections/db/sql/device/smb/SmbDeviceHandler.cpp +++ b/src/core-impl/collections/db/sql/device/smb/SmbDeviceHandler.cpp @@ -1,206 +1,207 @@ /**************************************************************************************** * Copyright (c) 2006-2007 Maximilian Kossick * * Copyright (c) 2011 Peter C. Ndikuwera * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "SmbDeviceHandler" #include "SmbDeviceHandler.h" #include "core/support/Debug.h" #include -#include +#include #include #include #include SmbDeviceHandler::SmbDeviceHandler( int deviceId, const QString &server, const QString &share, const QString &mountPoint, const QString &udi ) : DeviceHandler() , m_deviceID( deviceId ) , m_server( server ) , m_share( share ) , m_mountPoint( mountPoint ) , m_udi( udi ) { DEBUG_BLOCK } SmbDeviceHandler::SmbDeviceHandler( int deviceId, const QString &mountPoint, const QString &udi ) : DeviceHandler() , m_deviceID( deviceId ) , m_mountPoint( mountPoint ) , m_udi( udi ) { DEBUG_BLOCK } SmbDeviceHandler::~SmbDeviceHandler() { } bool SmbDeviceHandler::isAvailable() const { return true; } QString SmbDeviceHandler::type() const { return "smb"; } int SmbDeviceHandler::getDeviceID() { return m_deviceID; } const QString &SmbDeviceHandler::getDevicePath() const { return m_mountPoint; } -void SmbDeviceHandler::getURL( KUrl &absolutePath, const KUrl &relativePath ) +void SmbDeviceHandler::getURL( QUrl &absolutePath, const QUrl &relativePath ) { absolutePath.setPath( m_mountPoint ); - absolutePath.addPath( relativePath.path() ); + absolutePath = absolutePath.adjusted(QUrl::StripTrailingSlash); + absolutePath.setPath(absolutePath.path() + '/' + ( relativePath.path() )); absolutePath.cleanPath(); } -void SmbDeviceHandler::getPlayableURL( KUrl &absolutePath, const KUrl &relativePath ) +void SmbDeviceHandler::getPlayableURL( QUrl &absolutePath, const QUrl &relativePath ) { getURL( absolutePath, relativePath ); } bool SmbDeviceHandler::deviceMatchesUdi( const QString &udi ) const { return m_udi == udi; } /////////////////////////////////////////////////////////////////////////////// // class SmbDeviceHandlerFactory /////////////////////////////////////////////////////////////////////////////// QString SmbDeviceHandlerFactory::type( ) const { return "smb"; } bool SmbDeviceHandlerFactory::canCreateFromMedium( ) const { return true; } bool SmbDeviceHandlerFactory::canCreateFromConfig( ) const { return false; } bool SmbDeviceHandlerFactory::canHandle( const Solid::Device &device ) const { const Solid::NetworkShare *share = device.as(); if( !share ) { debug() << __PRETTY_FUNCTION__ << device.udi() << "has no NetworkShare interface"; return false; } if( share->type() != Solid::NetworkShare::Cifs ) { debug() << __PRETTY_FUNCTION__ << device.udi() << "has type" << share->type() << "but smbfs/cifs type is" << Solid::NetworkShare::Cifs; return false; } const Solid::StorageAccess *access = device.as(); if( !access ) { debug() << __PRETTY_FUNCTION__ << device.udi() << "has no StorageAccess interface"; return false; } if( !access->isAccessible() || access->filePath().isEmpty() ) { debug() << __PRETTY_FUNCTION__ << device.udi() << "is not accessible" << "or has empty mount-point"; return false; } return true; } SmbDeviceHandlerFactory::~SmbDeviceHandlerFactory( ) { } DeviceHandler * SmbDeviceHandlerFactory::createHandler( KSharedConfigPtr, SqlStorage* ) const { return 0; } DeviceHandler * SmbDeviceHandlerFactory::createHandler( const Solid::Device &device, const QString &udi, SqlStorage *s ) const { DEBUG_BLOCK if( !s ) { debug() << "!s, returning 0"; return 0; } if( !canHandle( device ) ) return 0; const Solid::StorageAccess *access = device.as(); Q_ASSERT( access ); // canHandle() checks it QString mountPoint = access->filePath(); const Solid::NetworkShare *netShare = device.as(); Q_ASSERT( netShare ); // canHandle() checks it QUrl url = netShare->url(); // smb://testanot/share2 QString server = url.host(); QString share = url.path().mid( 1 ); // strip leading slash, not usual in smb shares QStringList ids = s->query( QString( "SELECT id, label, lastmountpoint " "FROM devices WHERE type = 'smb' " "AND servername = '%1' AND sharename = '%2';" ) .arg( s->escape( server ) ) .arg( s->escape( share ) ) ); if ( ids.size() == 3 ) { debug() << "Found existing SMB config for ID " << ids[0] << " , server " << server << " ,share " << share; s->query( QString( "UPDATE devices SET lastmountpoint = '%2' WHERE " "id = %1;" ) .arg( ids[0] ) .arg( s->escape( mountPoint ) ) ); return new SmbDeviceHandler( ids[0].toInt(), server, share, mountPoint, udi ); } else { int id = s->insert( QString( "INSERT INTO devices" "( type, servername, sharename, lastmountpoint ) " "VALUES ( 'smb', '%1', '%2', '%3' );" ) .arg( s->escape( server ) ) .arg( s->escape( share ) ) .arg( s->escape( mountPoint ) ), "devices" ); if ( id == 0 ) { warning() << "Inserting into devices failed for type=smb, server=" << server << ", share=" << share; return 0; } debug() << "Created new SMB device with ID " << id << " , server " << server << " ,share " << share; return new SmbDeviceHandler( id, server, share, mountPoint, udi ); } } diff --git a/src/core-impl/collections/db/sql/device/smb/SmbDeviceHandler.h b/src/core-impl/collections/db/sql/device/smb/SmbDeviceHandler.h index c5a01038ba..b01a42c179 100644 --- a/src/core-impl/collections/db/sql/device/smb/SmbDeviceHandler.h +++ b/src/core-impl/collections/db/sql/device/smb/SmbDeviceHandler.h @@ -1,72 +1,72 @@ /**************************************************************************************** * Copyright (c) 2006-2007 Maximilian Kossick * * Copyright (c) 2011 Peter C. Ndikuwera * * * * 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, see . * ****************************************************************************************/ #ifndef SMBDEVICEHANDLER_H #define SMBDEVICEHANDLER_H #include "core-impl/collections/db/MountPointManager.h" class SmbDeviceHandlerFactory : public DeviceHandlerFactory { public: SmbDeviceHandlerFactory( QObject *parent ) : DeviceHandlerFactory( parent ) {} virtual ~SmbDeviceHandlerFactory(); virtual bool canHandle( const Solid::Device &device ) const; virtual bool canCreateFromMedium() const; virtual DeviceHandler* createHandler( const Solid::Device &device, const QString &uuid, SqlStorage *s ) const; virtual bool canCreateFromConfig() const; virtual DeviceHandler* createHandler( KSharedConfigPtr c, SqlStorage *s ) const; virtual QString type() const; }; /** @author Maximilian Kossick */ class SmbDeviceHandler : public DeviceHandler { public: SmbDeviceHandler(); SmbDeviceHandler(int deviceId, const QString &mountPoint, const QString &udi ); SmbDeviceHandler(int deviceId, const QString &server, const QString &share, const QString &mountPoint, const QString &udi ); virtual ~SmbDeviceHandler(); virtual bool isAvailable() const; virtual QString type() const; virtual int getDeviceID( ); virtual const QString &getDevicePath() const; - virtual void getURL( KUrl &absolutePath, const KUrl &relativePath ); - virtual void getPlayableURL( KUrl &absolutePath, const KUrl &relativePath ); + virtual void getURL( QUrl &absolutePath, const QUrl &relativePath ); + virtual void getPlayableURL( QUrl &absolutePath, const QUrl &relativePath ); virtual bool deviceMatchesUdi( const QString &udi ) const; private: int m_deviceID; QString m_server; QString m_share; const QString m_mountPoint; QString m_udi; }; #endif diff --git a/src/core-impl/collections/ipodcollection/IpodCollection.cpp b/src/core-impl/collections/ipodcollection/IpodCollection.cpp index 572b19267d..657d8e3267 100644 --- a/src/core-impl/collections/ipodcollection/IpodCollection.cpp +++ b/src/core-impl/collections/ipodcollection/IpodCollection.cpp @@ -1,700 +1,700 @@ /**************************************************************************************** * Copyright (c) 2012 MatÄ›j Laitl * * * * 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, see . * ****************************************************************************************/ #include "IpodCollection.h" #include "IpodCollectionLocation.h" #include "IpodMeta.h" #include "IpodPlaylistProvider.h" #include "jobs/IpodWriteDatabaseJob.h" #include "jobs/IpodParseTracksJob.h" #include "support/IphoneMountPoint.h" #include "support/IpodDeviceHelper.h" #include "support/IpodTranscodeCapability.h" #include "core/capabilities/ActionsCapability.h" #include "core/interfaces/Logger.h" #include "core/support/Components.h" #include "core/support/Debug.h" #include "core-impl/collections/support/MemoryCollection.h" #include "core-impl/collections/support/MemoryMeta.h" #include "core-impl/collections/support/MemoryQueryMaker.h" #include "playlistmanager/PlaylistManager.h" #include #include #include #include #include #include #include #include const QString IpodCollection::s_uidUrlProtocol = QString( "amarok-ipodtrackuid" ); const QStringList IpodCollection::s_audioFileTypes = QStringList() << "mp3" << "aac" << "m4a" /* MPEG-4 AAC and also ALAC */ << "m4b" /* audiobook */ << "aiff" << "wav"; const QStringList IpodCollection::s_videoFileTypes = QStringList() << "m4v" << "mov"; const QStringList IpodCollection::s_audioVideoFileTypes = QStringList() << "mp4"; IpodCollection::IpodCollection( const QDir &mountPoint, const QString &uuid ) : Collections::Collection() , m_configureDialog( 0 ) , m_mc( new Collections::MemoryCollection() ) , m_itdb( 0 ) , m_lastUpdated( 0 ) , m_preventUnmountTempFile( 0 ) , m_mountPoint( mountPoint.absolutePath() ) , m_uuid( uuid ) , m_iphoneAutoMountpoint( 0 ) , m_playlistProvider( 0 ) , m_configureAction( 0 ) , m_ejectAction( 0 ) , m_consolidateAction( 0 ) { DEBUG_BLOCK if( m_uuid.isEmpty() ) m_uuid = m_mountPoint; } IpodCollection::IpodCollection( const QString &uuid ) : Collections::Collection() , m_configureDialog( 0 ) , m_mc( new Collections::MemoryCollection() ) , m_itdb( 0 ) , m_lastUpdated( 0 ) , m_preventUnmountTempFile( 0 ) , m_uuid( uuid ) , m_playlistProvider( 0 ) , m_configureAction( 0 ) , m_ejectAction( 0 ) , m_consolidateAction( 0 ) { DEBUG_BLOCK // following constructor displays sorry message if it cannot mount iPhone: m_iphoneAutoMountpoint = new IphoneMountPoint( uuid ); m_mountPoint = m_iphoneAutoMountpoint->mountPoint(); if( m_uuid.isEmpty() ) m_uuid = m_mountPoint; } bool IpodCollection::init() { if( m_mountPoint.isEmpty() ) return false; // we have already displayed sorry message m_updateTimer.setSingleShot( true ); connect( this, SIGNAL(startUpdateTimer()), SLOT(slotStartUpdateTimer()) ); connect( &m_updateTimer, SIGNAL(timeout()), SLOT(collectionUpdated()) ); m_writeDatabaseTimer.setSingleShot( true ); connect( this, SIGNAL(startWriteDatabaseTimer()), SLOT(slotStartWriteDatabaseTimer()) ); connect( &m_writeDatabaseTimer, SIGNAL(timeout()), SLOT(slotInitiateDatabaseWrite()) ); m_configureAction = new QAction( KIcon( "configure" ), i18n( "&Configure Device" ), this ); m_configureAction->setProperty( "popupdropper_svg_id", "configure" ); connect( m_configureAction, SIGNAL(triggered()), SLOT(slotShowConfigureDialog()) ); m_ejectAction = new QAction( KIcon( "media-eject" ), i18n( "&Eject Device" ), this ); m_ejectAction->setProperty( "popupdropper_svg_id", "eject" ); connect( m_ejectAction, SIGNAL(triggered()), SLOT(slotEject()) ); QString parseErrorMessage; m_itdb = IpodDeviceHelper::parseItdb( m_mountPoint, parseErrorMessage ); m_prettyName = IpodDeviceHelper::collectionName( m_itdb ); // allows null m_itdb // m_consolidateAction is used by the provider m_consolidateAction = new QAction( KIcon( "dialog-ok-apply" ), i18n( "Re-add orphaned and forget stale tracks" ), this ); // provider needs to be up before IpodParseTracksJob is started m_playlistProvider = new IpodPlaylistProvider( this ); connect( m_playlistProvider, SIGNAL(startWriteDatabaseTimer()), SIGNAL(startWriteDatabaseTimer()) ); connect( m_consolidateAction, SIGNAL(triggered()), m_playlistProvider, SLOT(slotConsolidateStaleOrphaned()) ); The::playlistManager()->addProvider( m_playlistProvider, m_playlistProvider->category() ); if( m_itdb ) { // parse tracks in a thread in order not to block main thread IpodParseTracksJob *job = new IpodParseTracksJob( this ); m_parseTracksJob = job; connect( job, SIGNAL(done(ThreadWeaver::Job*)), job, SLOT(deleteLater()) ); ThreadWeaver::Weaver::instance()->enqueue( job ); } else slotShowConfigureDialog( parseErrorMessage ); // shows error message and allows initializing return true; // we have found iPod, even if it might not be initialised } IpodCollection::~IpodCollection() { DEBUG_BLOCK The::playlistManager()->removeProvider( m_playlistProvider ); // this is not racy: destructor should be called in a main thread, the timer fires in the // same thread if( m_writeDatabaseTimer.isActive() ) { m_writeDatabaseTimer.stop(); // call directly from main thread in destructor, we have no other chance: writeDatabase(); } delete m_preventUnmountTempFile; // this should have been certaily 0, but why not m_preventUnmountTempFile = 0; /* because m_itdb takes ownership of the tracks added to it, we need to remove the * tracks from itdb before we delete it because in Amarok, IpodMeta::Track is the owner * of the track */ IpodDeviceHelper::unlinkPlaylistsTracksFromItdb( m_itdb ); // does nothing if m_itdb is null itdb_free( m_itdb ); // does nothing if m_itdb is null m_itdb = 0; delete m_configureDialog; delete m_iphoneAutoMountpoint; // this can unmount iPhone and remove temporary dir } bool -IpodCollection::possiblyContainsTrack( const KUrl &url ) const +IpodCollection::possiblyContainsTrack( const QUrl &url ) const { return url.toLocalFile().startsWith( m_mountPoint ); } Meta::TrackPtr -IpodCollection::trackForUrl( const KUrl &url ) +IpodCollection::trackForUrl( const QUrl &url ) { QString relativePath = url.toLocalFile().mid( m_mountPoint.size() + 1 ); QString uidUrl = QString( "%1/%2" ).arg( collectionId(), relativePath ); return trackForUidUrl( uidUrl ); } bool IpodCollection::hasCapabilityInterface( Capabilities::Capability::Type type ) const { switch( type ) { case Capabilities::Capability::Actions: case Capabilities::Capability::Transcode: return true; default: break; } return false; } Capabilities::Capability* IpodCollection::createCapabilityInterface( Capabilities::Capability::Type type ) { switch( type ) { case Capabilities::Capability::Actions: { QList actions; if( m_configureAction ) actions << m_configureAction; if( m_ejectAction ) actions << m_ejectAction; if( m_consolidateAction && m_playlistProvider && m_playlistProvider->hasStaleOrOrphaned() ) actions << m_consolidateAction; return new Capabilities::ActionsCapability( actions ); } case Capabilities::Capability::Transcode: { gchar *deviceDirChar = itdb_get_device_dir( QFile::encodeName( m_mountPoint ) ); QString deviceDir = QFile::decodeName( deviceDirChar ); g_free( deviceDirChar ); return new Capabilities::IpodTranscodeCapability( this, deviceDir ); } default: break; } return 0; } Collections::QueryMaker* IpodCollection::queryMaker() { return new Collections::MemoryQueryMaker( m_mc.toWeakRef(), collectionId() ); } QString IpodCollection::uidUrlProtocol() const { return s_uidUrlProtocol; } QString IpodCollection::collectionId() const { return QString( "%1://%2" ).arg( s_uidUrlProtocol, m_uuid ); } QString IpodCollection::prettyName() const { return m_prettyName; } KIcon IpodCollection::icon() const { return KIcon("multimedia-player-apple-ipod"); } bool IpodCollection::hasCapacity() const { return KDiskFreeSpaceInfo::freeSpaceInfo( m_mountPoint ).isValid(); } float IpodCollection::usedCapacity() const { return KDiskFreeSpaceInfo::freeSpaceInfo( m_mountPoint ).used(); } float IpodCollection::totalCapacity() const { return KDiskFreeSpaceInfo::freeSpaceInfo( m_mountPoint ).size(); } Collections::CollectionLocation* IpodCollection::location() { return new IpodCollectionLocation( QWeakPointer( this ) ); } bool IpodCollection::isWritable() const { return IpodDeviceHelper::safeToWrite( m_mountPoint, m_itdb ); // returns false if m_itdb is null } bool IpodCollection::isOrganizable() const { return false; // iPods are never organizable } void IpodCollection::metadataChanged( Meta::TrackPtr track ) { // reflect change to ouside world: bool mapsChanged = MemoryMeta::MapChanger( m_mc.data() ).trackChanged( track ); if( mapsChanged ) // while docs say somehting different, collection browser doesn't update unless we emit updated() emit startUpdateTimer(); emit startWriteDatabaseTimer(); } QString IpodCollection::mountPoint() { return m_mountPoint; } float IpodCollection::capacityMargin() const { return 20*1024*1024; // 20 MiB } QStringList IpodCollection::supportedFormats() const { QStringList ret( s_audioFileTypes ); if( m_itdb && itdb_device_supports_video( m_itdb->device ) ) ret << s_videoFileTypes << s_audioVideoFileTypes; return ret; } Playlists::UserPlaylistProvider* IpodCollection::playlistProvider() const { return m_playlistProvider; } Meta::TrackPtr IpodCollection::trackForUidUrl( const QString &uidUrl ) { m_mc->acquireReadLock(); Meta::TrackPtr ret = m_mc->trackMap().value( uidUrl, Meta::TrackPtr() ); m_mc->releaseLock(); return ret; } void IpodCollection::slotDestroy() { // guard against user hitting the button twice or hitting it while there is another // write database job alreaddy running if( m_writeDatabaseJob ) { IpodWriteDatabaseJob *job = m_writeDatabaseJob.data(); // don't create duplicate connections: disconnect( job, SIGNAL(destroyed(QObject*)), this, SLOT(slotRemove()) ); disconnect( job, SIGNAL(destroyed(QObject*)), this, SLOT(slotPerformTeardownAndRemove()) ); connect( job, SIGNAL(destroyed(QObject*)), SLOT(slotRemove()) ); } // this is not racy: slotDestroy() is delivered to main thread, the timer fires in the // same thread else if( m_writeDatabaseTimer.isActive() ) { // write database in a thread so that it need not be written in destructor m_writeDatabaseTimer.stop(); IpodWriteDatabaseJob *job = new IpodWriteDatabaseJob( this ); m_writeDatabaseJob = job; connect( job, SIGNAL(done(ThreadWeaver::Job*)), job, SLOT(deleteLater()) ); connect( job, SIGNAL(destroyed(QObject*)), SLOT(slotRemove()) ); ThreadWeaver::Weaver::instance()->enqueue( job ); } else slotRemove(); } void IpodCollection::slotEject() { // guard against user hitting the button twice or hitting it while there is another // write database job alreaddy running if( m_writeDatabaseJob ) { IpodWriteDatabaseJob *job = m_writeDatabaseJob.data(); // don't create duplicate connections: disconnect( job, SIGNAL(destroyed(QObject*)), this, SLOT(slotRemove()) ); disconnect( job, SIGNAL(destroyed(QObject*)), this, SLOT(slotPerformTeardownAndRemove()) ); connect( job, SIGNAL(destroyed(QObject*)), SLOT(slotPerformTeardownAndRemove()) ); } // this is not racy: slotEject() is delivered to main thread, the timer fires in the // same thread else if( m_writeDatabaseTimer.isActive() ) { // write database now because iPod will be already unmounted in destructor m_writeDatabaseTimer.stop(); IpodWriteDatabaseJob *job = new IpodWriteDatabaseJob( this ); m_writeDatabaseJob = job; connect( job, SIGNAL(done(ThreadWeaver::Job*)), job, SLOT(deleteLater()) ); connect( job, SIGNAL(destroyed(QObject*)), SLOT(slotPerformTeardownAndRemove()) ); ThreadWeaver::Weaver::instance()->enqueue( job ); } else slotPerformTeardownAndRemove(); } void IpodCollection::slotShowConfigureDialog( const QString &errorMessage ) { if( !m_configureDialog ) { // create the dialog m_configureDialog = new KDialog(); QWidget *settingsWidget = new QWidget( m_configureDialog ); m_configureDialogUi.setupUi( settingsWidget ); m_configureDialog->setButtons( KDialog::Ok | KDialog::Cancel ); m_configureDialog->setMainWidget( settingsWidget ); m_configureDialog->setWindowTitle( settingsWidget->windowTitle() ); // setupUi() sets this if( m_itdb ) { // we will never initialize this iPod this time, hide ui for it completely m_configureDialogUi.modelComboLabel->hide(); m_configureDialogUi.modelComboBox->hide(); m_configureDialogUi.initializeLabel->hide(); m_configureDialogUi.initializeButton->hide(); } connect( m_configureDialogUi.initializeButton, SIGNAL(clicked(bool)), SLOT(slotInitialize()) ); connect( m_configureDialog, SIGNAL(okClicked()), SLOT(slotApplyConfiguration()) ); } QScopedPointer tc( create() ); IpodDeviceHelper::fillInConfigureDialog( m_configureDialog, &m_configureDialogUi, m_mountPoint, m_itdb, tc->savedConfiguration(), errorMessage ); // don't allow to resize the dialog too small: m_configureDialog->setMinimumSize( m_configureDialog->sizeHint() ); m_configureDialog->show(); m_configureDialog->raise(); } void IpodCollection::collectionUpdated() { m_lastUpdated = QDateTime::currentMSecsSinceEpoch(); emit updated(); } void IpodCollection::slotInitialize() { if( m_itdb ) return; // why the hell we were called? m_configureDialogUi.initializeButton->setEnabled( false ); QString errorMessage; bool success = IpodDeviceHelper::initializeIpod( m_mountPoint, &m_configureDialogUi, errorMessage ); if( !success ) { slotShowConfigureDialog( errorMessage ); return; } errorMessage.clear(); m_itdb = IpodDeviceHelper::parseItdb( m_mountPoint, errorMessage ); m_prettyName = IpodDeviceHelper::collectionName( m_itdb ); // allows null m_itdb if( m_itdb ) { QScopedPointer tc( create() ); errorMessage = i18nc( "iPod was successfully initialized", "Initialization successful." ); // so that the buttons are re-enabled, info filled etc: IpodDeviceHelper::fillInConfigureDialog( m_configureDialog, &m_configureDialogUi, m_mountPoint, m_itdb, tc->savedConfiguration(), errorMessage ); // there will be probably 0 tracks, but it may do more in future, for example stale // & orphaned track search. IpodParseTracksJob *job = new IpodParseTracksJob( this ); connect( job, SIGNAL(done(ThreadWeaver::Job*)), job, SLOT(deleteLater()) ); ThreadWeaver::Weaver::instance()->enqueue( job ); } else slotShowConfigureDialog( errorMessage ); // shows error message and allows initializing } void IpodCollection::slotApplyConfiguration() { if( !isWritable() ) return; // we can do nothing if we are not writeable QString newName = m_configureDialogUi.nameLineEdit->text(); if( !newName.isEmpty() && newName != IpodDeviceHelper::ipodName( m_itdb ) ) { IpodDeviceHelper::setIpodName( m_itdb, newName ); m_prettyName = IpodDeviceHelper::collectionName( m_itdb ); emit startWriteDatabaseTimer(); // the change should be written down to the database emit startUpdateTimer(); } QScopedPointer tc( create() ); tc->setSavedConfiguration( m_configureDialogUi.transcodeComboBox->currentChoice() ); } void IpodCollection::slotStartUpdateTimer() { // there are no concurrency problems, this method can only be called from the main // thread and that's where the timer fires if( m_updateTimer.isActive() ) return; // already running, nothing to do // number of milliseconds to next desired update, may be negative int timeout = m_lastUpdated + 1000 - QDateTime::currentMSecsSinceEpoch(); // give at least 50 msecs to catch multi-tracks edits nicely on the first frame m_updateTimer.start( qBound( 50, timeout, 1000 ) ); } void IpodCollection::slotStartWriteDatabaseTimer() { m_writeDatabaseTimer.start( 30000 ); // ensure we have a file on iPod open that prevents unmounting it if db is dirty if( !m_preventUnmountTempFile ) { m_preventUnmountTempFile = new QTemporaryFile(); QString name( "/.itunes_database_dirty_in_amarok_prevent_unmounting" ); m_preventUnmountTempFile->setFileTemplate( m_mountPoint + name ); m_preventUnmountTempFile->open(); } } void IpodCollection::slotInitiateDatabaseWrite() { if( m_writeDatabaseJob ) { warning() << __PRETTY_FUNCTION__ << "called while m_writeDatabaseJob still points" << "to an older job. Not doing anyhing."; return; } IpodWriteDatabaseJob *job = new IpodWriteDatabaseJob( this ); m_writeDatabaseJob = job; connect( job, SIGNAL(done(ThreadWeaver::Job*)), job, SLOT(deleteLater()) ); ThreadWeaver::Weaver::instance()->enqueue( job ); } void IpodCollection::slotPerformTeardownAndRemove() { /* try to eject the device from system. Following technique potentially catches more * cases than simply passing the udi from IpodCollectionFactory, think of fuse-based * filesystems for mounting iPhones et caetera.. */ Solid::Predicate query( Solid::DeviceInterface::StorageAccess, QString( "filePath" ), m_mountPoint ); QList devices = Solid::Device::listFromQuery( query ); if( devices.count() == 1 ) { Solid::Device device = devices.at( 0 ); Solid::StorageAccess *ssa = device.as(); if( ssa ) ssa->teardown(); } slotRemove(); } void IpodCollection::slotRemove() { // this is not racy, we are in the main thread and parseTracksJob can be deleted only // in the main thread if( m_parseTracksJob ) { // we need to wait until parseTracksJob finishes, because it acceses IpodCollection // and IpodPlaylistProvider in an asynchronous way that cannot safely cope with // IpodCollection disappearing connect( m_parseTracksJob.data(), SIGNAL(destroyed(QObject*)), SIGNAL(remove()) ); m_parseTracksJob.data()->abort(); } else emit remove(); } Meta::TrackPtr IpodCollection::addTrack( IpodMeta::Track *track ) { if( !track || !m_itdb ) return Meta::TrackPtr(); Itdb_Track *itdbTrack = track->itdbTrack(); bool justAdded = false; m_itdbMutex.lock(); Q_ASSERT( !itdbTrack->itdb || itdbTrack->itdb == m_itdb /* refuse to take track from another itdb */ ); if( !itdbTrack->itdb ) { itdb_track_add( m_itdb, itdbTrack, -1 ); // if it wasn't in itdb, it couldn't have legally been in master playlist // TODO: podcasts should not go into MPL itdb_playlist_add_track( itdb_playlist_mpl( m_itdb ), itdbTrack, -1 ); justAdded = true; emit startWriteDatabaseTimer(); } track->setCollection( QWeakPointer( this ) ); Meta::TrackPtr trackPtr( track ); Meta::TrackPtr memTrack = MemoryMeta::MapChanger( m_mc.data() ).addTrack( trackPtr ); if( !memTrack && justAdded ) { /* this new track was not added to MemoryCollection, it may vanish soon, prevent * dangling pointer in m_itdb */ itdb_playlist_remove_track( 0 /* = MPL */, itdbTrack ); itdb_track_unlink( itdbTrack ); } m_itdbMutex.unlock(); if( memTrack ) { subscribeTo( trackPtr ); emit startUpdateTimer(); } return memTrack; } void IpodCollection::removeTrack( const Meta::TrackPtr &track ) { if( !track ) return; // nothing to do /* Following call ensures thread-safety even when this method is called multiple times * from different threads with the same track: only one thread will get non-null * deletedTrack from MapChanger. */ Meta::TrackPtr deletedTrack = MemoryMeta::MapChanger( m_mc.data() ).removeTrack( track ); if( !deletedTrack ) { warning() << __PRETTY_FUNCTION__ << "attempt to delete a track that was not in" << "MemoryCollection or not added using MapChanger"; return; } IpodMeta::Track *ipodTrack = dynamic_cast( deletedTrack.data() ); if( !ipodTrack ) { warning() << __PRETTY_FUNCTION__ << "attempt to delete a track that was not" << "internally iPod track"; return; } Itdb_Track *itdbTrack = ipodTrack->itdbTrack(); if( itdbTrack->itdb && m_itdb ) { // remove from all playlists excluding the MPL: m_playlistProvider->removeTrackFromPlaylists( track ); QMutexLocker locker( &m_itdbMutex ); // remove track from the master playlist: itdb_playlist_remove_track( itdb_playlist_mpl( m_itdb ), itdbTrack ); // remove it from the db: itdb_track_unlink( itdbTrack ); emit startWriteDatabaseTimer(); } emit startUpdateTimer(); } bool IpodCollection::writeDatabase() { if( !IpodDeviceHelper::safeToWrite( m_mountPoint, m_itdb ) ) // returns false if m_itdb is null { // we have to delete unmount-preventing file even in this case delete m_preventUnmountTempFile; m_preventUnmountTempFile = 0; warning() << "Refusing to write iTunes database to iPod becauase device is not safe to write"; return false; } m_itdbMutex.lock(); GError *error = 0; bool success = itdb_write( m_itdb, &error ); m_itdbMutex.unlock(); QString gpodError; if( error ) { gpodError = QString::fromUtf8( error->message ); g_error_free( error ); error = 0; } delete m_preventUnmountTempFile; // this deletes the file m_preventUnmountTempFile = 0; if( success ) { QString message = i18nc( "%1: iPod collection name", "iTunes database successfully written to %1", prettyName() ); Amarok::Components::logger()->shortMessage( message ); } else { QString message; if( gpodError.isEmpty() ) message = i18nc( "%1: iPod collection name", "Writing iTunes database to %1 failed without an indication of error", prettyName() ); else message = i18nc( "%1: iPod collection name, %2: technical error from libgpod", "Writing iTunes database to %1 failed: %2", prettyName(), gpodError ); Amarok::Components::logger()->longMessage( message ); } return success; } diff --git a/src/core-impl/collections/ipodcollection/IpodCollection.h b/src/core-impl/collections/ipodcollection/IpodCollection.h index 14fdd40de6..7756f99fba 100644 --- a/src/core-impl/collections/ipodcollection/IpodCollection.h +++ b/src/core-impl/collections/ipodcollection/IpodCollection.h @@ -1,282 +1,282 @@ /**************************************************************************************** * Copyright (c) 2012 MatÄ›j Laitl . * ****************************************************************************************/ #ifndef IPODCOLLECTION_H #define IPODCOLLECTION_H #include "ui_IpodConfiguration.h" #include "core/collections/Collection.h" #include "core/meta/Observer.h" #include #include #include namespace Collections { class MemoryCollection; } namespace IpodMeta { class Track; } class IphoneMountPoint; class IpodParseTracksJob; class IpodWriteDatabaseJob; class IpodPlaylistProvider; class QDir; class QTemporaryFile; struct _Itdb_iTunesDB; typedef _Itdb_iTunesDB Itdb_iTunesDB; class IpodCollection : public Collections::Collection, public Meta::Observer { Q_OBJECT public: /** * Creates an iPod collection on top of already-mounted filesystem. * * @param mountPoint actual iPod mount point to use, must be already mounted and * accessible. When eject is requested, solid StorageAccess with this mount point * is searched for to perform unmounting. * @param uuid filesystem volume UUID or another unique identifier for this iPod */ explicit IpodCollection( const QDir &mountPoint, const QString &uuid ); /** * Creates an iPod collection on top of not-mounted iPhone/iPad by accessing it * using libimobiledevice by its 40-digit device UUID. UUID may be empty which * means "any connected iPhone/iPad". */ explicit IpodCollection( const QString &uuid ); virtual ~IpodCollection(); // TrackProvider methods: - virtual bool possiblyContainsTrack( const KUrl &url ) const; - virtual Meta::TrackPtr trackForUrl( const KUrl &url ); + virtual bool possiblyContainsTrack( const QUrl &url ) const; + virtual Meta::TrackPtr trackForUrl( const QUrl &url ); // CollectionBase methods: virtual bool hasCapabilityInterface( Capabilities::Capability::Type type ) const; virtual Capabilities::Capability* createCapabilityInterface( Capabilities::Capability::Type type ); // Collection methods: virtual Collections::QueryMaker *queryMaker(); virtual QString uidUrlProtocol() const; virtual QString collectionId() const; virtual QString prettyName() const; virtual KIcon icon() const; virtual bool hasCapacity() const; virtual float usedCapacity() const; virtual float totalCapacity() const; virtual Collections::CollectionLocation *location(); virtual bool isWritable() const; virtual bool isOrganizable() const; // Observer methods: virtual void metadataChanged( Meta::TrackPtr track ); // so that the compiler doesn't complain about hidden virtual functions: using Meta::Observer::metadataChanged; // IpodCollection methods: /** * In-fact second phase of the construcor. Called by CollectionFactory right after * constructor. Should return true if the collection initialised itself successfully * and should be shown to the user; return value of false means it should be * destroyed and forgotten by the factory. */ bool init(); /** * Get local mount point. Can return QString() in case no reasonamble mountpoint * is available */ QString mountPoint(); /** * Return number of bytes that should be kept free in iPod for database operations. * CollectionLocation should try hard not to occupy this safety margin. */ float capacityMargin() const; /** * Return a list of file formats (compatible with Meta::Track::type()) current iPod * is able to play. */ QStringList supportedFormats() const; /** * Return pointer to playlist provider associated with this iPod. May be null in * special cases (iPod not yet initialised etc.) */ Playlists::UserPlaylistProvider *playlistProvider() const; Meta::TrackPtr trackForUidUrl( const QString &uidUrl ); signals: /** * Start a count-down that emits updated() signal after it expires. * Resets the timer to original timeout if already running. This is to ensure * that we emit update() max. once per for batch updates. * * Timers can only be started from "their" thread so use signals & slots for that. */ void startUpdateTimer(); /** * Start a count-down that initiates iTunes database wrtiging after it expires. * Resets the timer to original timeout if already running. This is to ensure * that we don't write the database all the time for batch updates. * * Timers can only be started from "their" thread so use signals & slots for that. */ void startWriteDatabaseTimer(); public slots: /** * Destroy the collection, try to write back iTunes database (if dirty) */ void slotDestroy(); /** * Destroy the collection, write back iTunes db (if dirty) and try to eject the * iPod from system */ void slotEject(); /** * Shows the configuration dialog in a non-modal window. If m_itdb is null, shows * some info and a button to try to initialize iPod. */ void slotShowConfigureDialog( const QString &errorMessage = QString() ); private slots: /** * Update m_lastUpdated timestamp and emit updated() */ void collectionUpdated(); /** * Tries to initialize iPod, read the database, add tracks. (Re)shows the * configuration dialog with info about initialization. */ void slotInitialize(); /** * Sets iPod name to the name in configure dialog. */ void slotApplyConfiguration(); /** * Starts a timer that ensures we emit updated() signal sometime in future. */ void slotStartUpdateTimer(); /** * Starts a timer that initiates iTunes database writing after 30 seconds. */ void slotStartWriteDatabaseTimer(); /** * Enqueues a job in a thread that writes iTunes database back to iPod. Should * only be called from m_writeDatabaseTimer's timeout() signal. (with exception * when IpodCollection is about to destroy itself) */ void slotInitiateDatabaseWrite(); /** * Tries to unmount underlying solid device. You must try to write database before * calling this. Emits remove() before returning. */ void slotPerformTeardownAndRemove(); /** * Do sanity checks and emit remove() so that this collection is destroyed by * CollectionManager. No other method is allowed to emit remove()! */ void slotRemove(); private: friend class IpodCopyTracksJob; friend class IpodDeleteTracksJob; friend class IpodParseTracksJob; friend class IpodWriteDatabaseJob; friend class IpodPlaylistProvider; static const QString s_uidUrlProtocol; static const QStringList s_audioFileTypes; static const QStringList s_videoFileTypes; static const QStringList s_audioVideoFileTypes; // method for IpodParseTracksJob and IpodCopyTracksJob: /** * Add an iPod track to the collection. * * This method adds it to the collection, master playlist (if not already there) * etc. The file must be already physically copied to iPod. (Re)Sets track's * collection to this collection. Takes ownership of the track (passes it to * KSharedPtr) * * This method is thread-safe. * * @return pointer to newly added track if successful, null pointer otherwise */ Meta::TrackPtr addTrack( IpodMeta::Track *track ); // method for IpodDeleteTracksJob: /** * Removes a track from iPod collection. Does not delete the file physically, * caller must do it after calling this method. * * @param track a track from associated MemoryCollection to delete. Accepts also * underlying IpodMeta::Track, this is treated as if MemoryMeta::Track track * proxy it was passed. * * This method is thread-safe. */ void removeTrack( const Meta::TrackPtr &track ); // method for IpodWriteDatabaseJob and destructor: /** * Calls itdb_write() directly. Logs a message about success/failure in Amarok * interface. */ bool writeDatabase(); KDialog *m_configureDialog; Ui::IpodConfiguration m_configureDialogUi; QSharedPointer m_mc; /** * pointer to libgpod iTunes database. If null, this collection is invalid * (not yet initialised). Can only be changed with m_itdbMutex hold. */ Itdb_iTunesDB *m_itdb; QMutex m_itdbMutex; QTimer m_updateTimer; qint64 m_lastUpdated; /* msecs since epoch */ QTimer m_writeDatabaseTimer; QTemporaryFile *m_preventUnmountTempFile; QString m_mountPoint; QString m_uuid; IphoneMountPoint *m_iphoneAutoMountpoint; QString m_prettyName; IpodPlaylistProvider *m_playlistProvider; QAction *m_configureAction; QAction *m_ejectAction; QAction *m_consolidateAction; QWeakPointer m_parseTracksJob; QWeakPointer m_writeDatabaseJob; }; #endif // IPODCOLLECTION_H diff --git a/src/core-impl/collections/ipodcollection/IpodCollectionLocation.cpp b/src/core-impl/collections/ipodcollection/IpodCollectionLocation.cpp index d773d1f79e..8e8406e1dc 100644 --- a/src/core-impl/collections/ipodcollection/IpodCollectionLocation.cpp +++ b/src/core-impl/collections/ipodcollection/IpodCollectionLocation.cpp @@ -1,153 +1,153 @@ /**************************************************************************************** * Copyright (c) 2012 MatÄ›j Laitl * * * * 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, see . * ****************************************************************************************/ #include "IpodCollectionLocation.h" #include "jobs/IpodDeleteTracksJob.h" #include "core/interfaces/Logger.h" #include "core/support/Components.h" #include "core/support/Debug.h" #include #include #include #include IpodCollectionLocation::IpodCollectionLocation( QWeakPointer parentCollection ) : CollectionLocation() // we implement collection(), we need not pass parentCollection , m_coll( parentCollection ) { } IpodCollectionLocation::~IpodCollectionLocation() { } Collections::Collection* IpodCollectionLocation::collection() const { // overridden to avoid dangling pointers return m_coll.data(); } QString IpodCollectionLocation::prettyLocation() const { if( m_coll ) return m_coll.data()->prettyName(); // match string with IpodCopyTracksJob::slotDisplaySorryDialog() return i18n( "Disconnected iPod/iPad/iPhone" ); } bool IpodCollectionLocation::isWritable() const { if( !m_coll ) return false; return m_coll.data()->isWritable(); // no infinite loop, IpodCollection iplements this } void -IpodCollectionLocation::copyUrlsToCollection( const QMap &sources, +IpodCollectionLocation::copyUrlsToCollection( const QMap &sources, const Transcoding::Configuration &configuration ) { if( !isWritable() ) return; // mostly unreachable, CollectionLocation already checks this and issues a warning ensureDirectoriesExist(); IpodCopyTracksJob *job = new IpodCopyTracksJob( sources, m_coll, configuration, isGoingToRemoveSources() ); int trackCount = sources.size(); Amarok::Components::logger()->newProgressOperation( job, operationInProgressText( configuration, trackCount ), trackCount, job, SLOT(abort()) ); qRegisterMetaType( "IpodCopyTracksJob::CopiedStatus" ); connect( job, SIGNAL(signalTrackProcessed(Meta::TrackPtr,Meta::TrackPtr,IpodCopyTracksJob::CopiedStatus)), this, SLOT(slotCopyTrackProcessed(Meta::TrackPtr,Meta::TrackPtr,IpodCopyTracksJob::CopiedStatus)) ); connect( job, SIGNAL(done(ThreadWeaver::Job*)), this, SLOT(slotCopyOperationFinished()) ); connect( job, SIGNAL(done(ThreadWeaver::Job*)), job, SLOT(deleteLater()) ); ThreadWeaver::Weaver::instance()->enqueue( job ); } void IpodCollectionLocation::removeUrlsFromCollection( const Meta::TrackList &sources ) { if( !isWritable() ) return; IpodDeleteTracksJob *job = new IpodDeleteTracksJob( sources, m_coll ); connect( job, SIGNAL(done(ThreadWeaver::Job*)), this, SLOT(slotRemoveOperationFinished()) ); connect( job, SIGNAL(done(ThreadWeaver::Job*)), job, SLOT(deleteLater()) ); ThreadWeaver::Weaver::instance()->enqueue( job ); } void IpodCollectionLocation::setDestinationPlaylist( Playlists::PlaylistPtr destPlaylist, const QMap &trackPlaylistPositions ) { m_destPlaylist = destPlaylist; m_trackPlaylistPositions = trackPlaylistPositions; } void IpodCollectionLocation::slotCopyTrackProcessed( Meta::TrackPtr srcTrack, Meta::TrackPtr destTrack, IpodCopyTracksJob::CopiedStatus status ) { if( status == IpodCopyTracksJob::Success ) // we do not include track found by matching meta-data here for safety reasons source()->transferSuccessful( srcTrack ); if( m_destPlaylist && ( status == IpodCopyTracksJob::Success || status == IpodCopyTracksJob::Duplicate ) && destTrack && m_trackPlaylistPositions.contains( srcTrack ) ) // add this track to iPod playlist { m_destPlaylist->addTrack( destTrack, m_trackPlaylistPositions.value( srcTrack ) ); } } void IpodCollectionLocation::ensureDirectoriesExist() { QByteArray mountPoint = m_coll ? QFile::encodeName( m_coll.data()->mountPoint() ) : QByteArray(); if( mountPoint.isEmpty() ) return; gchar *musicDirChar = itdb_get_music_dir( mountPoint.constData() ); QString musicDirPath = QFile::decodeName( musicDirChar ); g_free( musicDirChar ); if( musicDirPath.isEmpty() ) return; QDir musicDir( musicDirPath ); if( !musicDir.exists() && !musicDir.mkpath( "." ) /* try to create it */ ) { warning() << __PRETTY_FUNCTION__ << "failed to create" << musicDirPath << "directory."; return; } QChar fillChar( '0' ); for( int i = 0; i < 20; i++ ) { QString name = QString( "F%1" ).arg( i, /* min-width */ 2, /* base */ 10, fillChar ); if( musicDir.exists( name ) ) continue; QString toCreatePath = QString( "%1/%2" ).arg( musicDirPath, name ); if( musicDir.mkdir( name ) ) debug() << __PRETTY_FUNCTION__ << "created" << toCreatePath << "directory."; else warning() << __PRETTY_FUNCTION__ << "failed to create" << toCreatePath << "directory."; } } diff --git a/src/core-impl/collections/ipodcollection/IpodCollectionLocation.h b/src/core-impl/collections/ipodcollection/IpodCollectionLocation.h index cc27e19bb6..6298bfeb1e 100644 --- a/src/core-impl/collections/ipodcollection/IpodCollectionLocation.h +++ b/src/core-impl/collections/ipodcollection/IpodCollectionLocation.h @@ -1,75 +1,75 @@ /**************************************************************************************** * Copyright (c) 2012 MatÄ›j Laitl * * * * 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, see . * ****************************************************************************************/ #ifndef IPODCOLLECTIONLOCATION_H #define IPODCOLLECTIONLOCATION_H #include "IpodCollection.h" #include "jobs/IpodCopyTracksJob.h" #include "core/collections/CollectionLocation.h" #include "core/playlists/Playlist.h" #include class IpodCollectionLocation : public Collections::CollectionLocation { Q_OBJECT public: IpodCollectionLocation( QWeakPointer parentCollection ); virtual ~IpodCollectionLocation(); // CollectionLocation methods: virtual Collections::Collection *collection() const; virtual QString prettyLocation() const; virtual bool isWritable() const; - virtual void copyUrlsToCollection( const QMap &sources, + virtual void copyUrlsToCollection( const QMap &sources, const Transcoding::Configuration &configuration ); virtual void removeUrlsFromCollection( const Meta::TrackList &sources ); // IpodCollectionLocation specific methods: /** * Calling this causes that when the tracks are copied, they are added to iPod * playlist @param playlist */ void setDestinationPlaylist( Playlists::PlaylistPtr destPlaylist, const QMap &trackPlaylistPositions ); /** * This method is published so that IpodPlaylistProvider can hide removal dialog. */ using Collections::CollectionLocation::setHidingRemoveConfirm; private slots: void slotCopyTrackProcessed( Meta::TrackPtr srcTrack, Meta::TrackPtr destTrack, IpodCopyTracksJob::CopiedStatus status ); private: /** * Helper method to create a basic set of /iPod_Contol/Music/F.. directories * so that copying doesn't fail for this simple reason */ void ensureDirectoriesExist(); QWeakPointer m_coll; QMap m_trackPlaylistPositions; Playlists::PlaylistPtr m_destPlaylist; }; #endif // IPODCOLLECTIONLOCATION_H diff --git a/src/core-impl/collections/ipodcollection/IpodMeta.cpp b/src/core-impl/collections/ipodcollection/IpodMeta.cpp index 6beca0abad..7e0d11634d 100644 --- a/src/core-impl/collections/ipodcollection/IpodMeta.cpp +++ b/src/core-impl/collections/ipodcollection/IpodMeta.cpp @@ -1,888 +1,888 @@ /**************************************************************************************** * Copyright (c) 2012 MatÄ›j Laitl . * ****************************************************************************************/ #include "IpodMeta.h" #include "amarokconfig.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include "core-impl/collections/ipodcollection/IpodCollection.h" #include "core-impl/collections/ipodcollection/config-ipodcollection.h" #include "core-impl/collections/support/jobs/WriteTagsJob.h" #include "core-impl/collections/support/ArtistHelper.h" #include "covermanager/CoverCache.h" #include "FileType.h" #include #include #include #include #ifdef GDKPIXBUF_FOUND #undef signals // gdbusintrospection.h uses a member named signals, prevent build fail #include #include #endif using namespace IpodMeta; gpointer AmarokItdbUserDataDuplicateFunc( gpointer userdata ) { Q_UNUSED( userdata ) return 0; // we never copy our userdata } Track::Track( Itdb_Track *ipodTrack ) : m_track( ipodTrack ) , m_batch( 0 ) { Q_ASSERT( m_track != 0 ); m_track->usertype = m_gpodTrackUserTypeAmarokTrackPtr; m_track->userdata = this; m_track->userdata_duplicate = AmarokItdbUserDataDuplicateFunc; } Track::Track( const Meta::TrackPtr &origTrack ) : m_track( itdb_track_new() ) , m_batch( 0 ) { Q_ASSERT( m_track != 0 ); m_track->usertype = m_gpodTrackUserTypeAmarokTrackPtr; m_track->userdata = this; m_track->userdata_duplicate = AmarokItdbUserDataDuplicateFunc; Meta::AlbumPtr origAlbum = origTrack->album(); Meta::ArtistPtr origArtist = origTrack->artist(); beginUpdate(); setTitle( origTrack->name() ); // url is set in setCollection() setAlbum( origAlbum ? origAlbum->name() : QString() ); setArtist( origArtist ? origArtist->name() : QString() ); setComposer( origTrack->composer() ? origTrack->composer()->name() : QString() ); setGenre( origTrack->genre() ? origTrack->genre()->name() : QString() ); setYear( origTrack->year() ? origTrack->year()->year() : 0 ); QString albumArtist; bool isCompilation = false; if ( origAlbum ) { isCompilation = origAlbum->isCompilation(); if( origAlbum->hasAlbumArtist() && origAlbum->albumArtist() ) albumArtist = origAlbum->albumArtist()->name(); if( origAlbum->hasImage() ) setImage( origAlbum->image() ); } /* iPod doesn't handle empty album artist well for compilation albums (splits these * albums). Ensure that we have something in albumArtist. We filter it for Amarok for * compilation albums in IpodMeta::Album::albumArtist() */ if( albumArtist.isEmpty() && origArtist ) albumArtist = origArtist->name(); if( albumArtist.isEmpty() ) albumArtist = i18n( "Various Artists" ); Meta::ConstStatisticsPtr origStats = origTrack->statistics(); setAlbumArtist( albumArtist ); setIsCompilation( isCompilation ); setBpm( origTrack->bpm() ); setComment( origTrack->comment() ); setScore( origStats->score() ); setRating( origStats->rating() ); setLength( origTrack->length() ); // filesize is set in finalizeCopying(), which could be more accurate setSampleRate( origTrack->sampleRate() ); setBitrate( origTrack->bitrate() ); setCreateDate( QDateTime::currentDateTime() ); // createDate == added to collection setModifyDate( origTrack->modifyDate() ); setTrackNumber( origTrack->trackNumber() ); setDiscNumber( origTrack->discNumber() ); setLastPlayed( origStats->lastPlayed() ); setFirstPlayed( origStats->firstPlayed() ); setPlayCount( origStats->playCount() ); setReplayGain( Meta::ReplayGain_Track_Gain, origTrack->replayGain( Meta::ReplayGain_Track_Gain ) ); setReplayGain( Meta::ReplayGain_Track_Peak, origTrack->replayGain( Meta::ReplayGain_Track_Peak ) ); setReplayGain( Meta::ReplayGain_Album_Gain, origTrack->replayGain( Meta::ReplayGain_Album_Gain ) ); setReplayGain( Meta::ReplayGain_Album_Peak, origTrack->replayGain( Meta::ReplayGain_Album_Peak ) ); setType( origTrack->type() ); m_changedFields.clear(); // some of the set{Something} insert to m_changedFields, not // desirable for constructor endUpdate(); } Track::~Track() { itdb_track_free( m_track ); if( !m_tempImageFilePath.isEmpty() ) QFile::remove( m_tempImageFilePath ); } QString Track::name() const { QReadLocker locker( &m_trackLock ); return QString::fromUtf8( m_track->title ); } void Track::setTitle( const QString &newTitle ) { QWriteLocker locker( &m_trackLock ); g_free( m_track->title ); m_track->title = g_strdup( newTitle.toUtf8() ); commitIfInNonBatchUpdate( Meta::valTitle, newTitle ); } -KUrl +QUrl Track::playableUrl() const { if( m_mountPoint.isEmpty() || !m_track->ipod_path || m_track->ipod_path[0] == '\0' ) - return KUrl(); + return QUrl(); QReadLocker locker( &m_trackLock ); gchar *relPathChar = g_strdup( m_track->ipod_path ); locker.unlock(); itdb_filename_ipod2fs( relPathChar ); // in-place // relPath begins with a slash QString relPath = QFile::decodeName( relPathChar ); g_free( relPathChar ); - return KUrl( m_mountPoint + relPath ); + return QUrl( m_mountPoint + relPath ); } QString Track::prettyUrl() const { - const KUrl &url = playableUrl(); + const QUrl &url = playableUrl(); if( url.isLocalFile() ) return url.toLocalFile(); QString collName = m_coll ? m_coll.data()->prettyName() : i18n( "Unknown Collection" ); QString artistName = artist() ? artist()->prettyName() : i18n( "Unknown Artist" ); QString trackName = !name().isEmpty() ? name() : i18n( "Unknown track" ); return QString( "%1: %2 - %3" ).arg( collName, artistName, trackName ); } QString Track::uidUrl() const { QReadLocker locker( &m_trackLock ); gchar *relPathChar = g_strdup( m_track->ipod_path ); locker.unlock(); itdb_filename_ipod2fs( relPathChar ); // in-place // relPath begins with a slash QString relPath = QFile::decodeName( relPathChar ); g_free( relPathChar ); if( m_coll ) return m_coll.data()->collectionId() + relPath; else return m_mountPoint + relPath; } QString Track::notPlayableReason() const { return localFileNotPlayableReason( playableUrl().toLocalFile() ); } Meta::AlbumPtr Track::album() const { // we may not store KSharedPtr to Album because it would create circular reference return Meta::AlbumPtr( new Album( const_cast( this ) ) ); } void Track::setAlbum( const QString &newAlbum ) { QWriteLocker locker( &m_trackLock ); g_free( m_track->album ); m_track->album = g_strdup( newAlbum.toUtf8() ); commitIfInNonBatchUpdate( Meta::valAlbum, newAlbum ); } void Track::setAlbumArtist( const QString &newAlbumArtist ) { QWriteLocker locker( &m_trackLock ); g_free( m_track->albumartist ); m_track->albumartist = g_strdup( newAlbumArtist.toUtf8() ); commitIfInNonBatchUpdate( Meta::valAlbumArtist, newAlbumArtist ); } void Track::setIsCompilation( bool newIsCompilation ) { // libgpod says: m_track->combination: True if set to 0x1, false if set to 0x0. if( m_track->compilation == newIsCompilation ) return; // nothing to do QWriteLocker locker( &m_trackLock ); m_track->compilation = newIsCompilation ? 0x1 : 0x0; commitIfInNonBatchUpdate( Meta::valCompilation, newIsCompilation ); } void Track::setImage( const QImage &newImage ) { QWriteLocker locker( &m_trackLock ); if( !m_tempImageFilePath.isEmpty() ) QFile::remove( m_tempImageFilePath ); m_tempImageFilePath.clear(); if( newImage.isNull() ) itdb_track_remove_thumbnails( m_track ); else { // we set artwork even for devices that don't support it, everyone has new-enough iPod nowadays const int maxSize = AmarokConfig::writeBackCoverDimensions(); QImage image; if( newImage.width() > maxSize || newImage.height() > maxSize ) image = newImage.scaled( maxSize, maxSize, Qt::KeepAspectRatio, Qt::SmoothTransformation ); else image = newImage; KTemporaryFile tempImageFile; tempImageFile.setAutoRemove( false ); // file will be removed in ~Track() tempImageFile.setSuffix( QString( ".png" ) ); // we save the file to disk rather than pass image data to save several megabytes of RAM if( tempImageFile.open() ) m_tempImageFilePath = tempImageFile.fileName(); if( tempImageFile.isOpen() && image.save( &tempImageFile, "PNG" ) ) /* this function remembers image path, it also fogots previous images (if any) * and sets artwork_size, artwork_count and has_artwork m_track fields */ itdb_track_set_thumbnails( m_track, QFile::encodeName( m_tempImageFilePath ) ); } commitIfInNonBatchUpdate( Meta::valImage, newImage ); } Meta::ArtistPtr Track::artist() const { QReadLocker locker( &m_trackLock ); return Meta::ArtistPtr( new Artist( QString::fromUtf8( m_track->artist ) ) ); } void Track::setArtist( const QString &newArtist ) { QWriteLocker locker( &m_trackLock ); g_free( m_track->artist ); m_track->artist = g_strdup( newArtist.toUtf8() ); commitIfInNonBatchUpdate( Meta::valArtist, newArtist ); } Meta::ComposerPtr Track::composer() const { QReadLocker locker( &m_trackLock ); return Meta::ComposerPtr( new Composer( QString::fromUtf8( m_track->composer ) ) ); } void Track::setComposer( const QString &newComposer ) { QWriteLocker locker( &m_trackLock ); g_free( m_track->composer ); m_track->composer = g_strdup( newComposer.toUtf8() ); commitIfInNonBatchUpdate( Meta::valComposer, newComposer ); } Meta::GenrePtr Track::genre() const { QReadLocker locker( &m_trackLock ); return Meta::GenrePtr( new Genre( QString::fromUtf8( m_track->genre ) ) ); } void Track::setGenre( const QString &newGenre ) { QWriteLocker locker( &m_trackLock ); g_free( m_track->genre ); m_track->genre = g_strdup( newGenre.toUtf8() ); commitIfInNonBatchUpdate( Meta::valGenre, newGenre ); } Meta::YearPtr Track::year() const { // no need for lock here, reading integer should be atomic return Meta::YearPtr( new Year( QString::number( m_track->year ) ) ); } void Track::setYear( int newYear ) { QWriteLocker locker( &m_trackLock ); m_track->year = newYear; commitIfInNonBatchUpdate( Meta::valYear, newYear ); } qreal Track::bpm() const { // no need for lock here, integer read return m_track->BPM; } void Track::setBpm( const qreal newBpm ) { QWriteLocker locker( &m_trackLock ); m_track->BPM = newBpm; commitIfInNonBatchUpdate( Meta::valBpm, newBpm ); } QString Track::comment() const { QReadLocker locker( &m_trackLock ); return QString::fromUtf8( m_track->comment ); } void Track::setComment( const QString &newComment ) { QWriteLocker locker( &m_trackLock ); g_free( m_track->comment ); m_track->comment = g_strdup( newComment.toUtf8() ); commitIfInNonBatchUpdate( Meta::valComment, newComment ); } int Track::rating() const { /* (rating/RATING_STEP) is a number of stars, Amarok uses numer of half-stars. * the order of multiply and divide operations is significant because of rounding */ return ( ( m_track->rating * 2 ) / ITDB_RATING_STEP ); } void Track::setRating( int newRating ) { newRating = ( newRating * ITDB_RATING_STEP ) / 2; if( newRating == (int) m_track->rating ) // casting prevents compiler waring about signedness return; // nothing to do, do not notify observers QWriteLocker locker( &m_trackLock ); m_track->rating = newRating; commitIfInNonBatchUpdate( Meta::valRating, newRating ); } qint64 Track::length() const { return m_track->tracklen; } void Track::setLength( qint64 newLength ) { QWriteLocker locker( &m_trackLock ); m_track->tracklen = newLength; commitIfInNonBatchUpdate( Meta::valLength, newLength ); } int Track::filesize() const { return m_track->size; } int Track::sampleRate() const { return m_track->samplerate; } void Track::setSampleRate( int newSampleRate ) { QWriteLocker locker( &m_trackLock ); m_track->samplerate = newSampleRate; commitIfInNonBatchUpdate( Meta::valSamplerate, newSampleRate ); } int Track::bitrate() const { return m_track->bitrate; } void Track::setBitrate( int newBitrate ) { QWriteLocker locker( &m_trackLock ); m_track->bitrate = newBitrate; commitIfInNonBatchUpdate( Meta::valBitrate, newBitrate ); } QDateTime Track::createDate() const { time_t time = m_track->time_added; if( time == 0 ) return QDateTime(); // 0 means "no reasonable time", so return invalid QDateTime return QDateTime::fromTime_t( time ); } void Track::setCreateDate( const QDateTime &newDate ) { QWriteLocker locker( &m_trackLock ); m_track->time_added = newDate.isValid() ? newDate.toTime_t() : 0; commitIfInNonBatchUpdate( Meta::valCreateDate, newDate ); } QDateTime Track::modifyDate() const { time_t time = m_track->time_modified; if( time == 0 ) return QDateTime(); // 0 means "no reasonable time", so return invalid QDateTime return QDateTime::fromTime_t( time ); } void Track::setModifyDate( const QDateTime &newDate ) { // this method _cannot_ lock m_trackLock or deadlock will occur in commitChanges() m_track->time_modified = newDate.isValid() ? newDate.toTime_t() : 0; } int Track::trackNumber() const { // no need for lock here, integer read return m_track->track_nr; } void Track::setTrackNumber( int newTrackNumber ) { QWriteLocker locker( &m_trackLock ); m_track->track_nr = newTrackNumber; commitIfInNonBatchUpdate( Meta::valTrackNr, newTrackNumber ); } int Track::discNumber() const { // no need for lock here, integer read return m_track->cd_nr; } void Track::setDiscNumber( int newDiscNumber ) { QWriteLocker locker( &m_trackLock ); m_track->cd_nr = newDiscNumber; commitIfInNonBatchUpdate( Meta::valDiscNr, newDiscNumber ); } QDateTime Track::lastPlayed() const { return m_track->time_played ? QDateTime::fromTime_t( m_track->time_played ) : QDateTime(); } void Track::setLastPlayed( const QDateTime &time ) { QWriteLocker locker( &m_trackLock ); m_track->time_played = time.isValid() ? time.toTime_t() : 0; commitIfInNonBatchUpdate( Meta::valLastPlayed, time ); } QDateTime Track::firstPlayed() const { // we abuse time_released for this, which should be okay for non-podcast tracks // TODO: return QDateTime for podcast tracks return m_track->time_released ? QDateTime::fromTime_t( m_track->time_released ) : QDateTime(); } void Track::setFirstPlayed( const QDateTime &time ) { QWriteLocker locker( &m_trackLock ); m_track->time_released = time.isValid() ? time.toTime_t() : 0; commitIfInNonBatchUpdate( Meta::valFirstPlayed, time ); } int Track::playCount() const { return m_track->playcount; } int Track::recentPlayCount() const { if( !m_coll || !m_coll.data()->isWritable() ) return 0; // we must be able to reset recent playcount if we return nonzero return m_track->recent_playcount; } void Track::setPlayCount( const int playcount ) { QWriteLocker locker( &m_trackLock ); m_track->playcount = playcount; m_track->recent_playcount = 0; commitIfInNonBatchUpdate( Meta::valLastPlayed, playcount ); } qreal Track::replayGain( Meta::ReplayGainTag mode ) const { // iPods are not able to differentiante between different replay gain modes (track & album) switch( mode ) { case Meta::ReplayGain_Track_Gain: case Meta::ReplayGain_Album_Gain: break; // fall to the computation case Meta::ReplayGain_Track_Peak: case Meta::ReplayGain_Album_Peak: return 0.0; // perhaps return -replayGain assuming there was enough headroom } if( m_track->soundcheck == 0 ) return 0.0; // libgpod: The value 0 is special, treated as "no Soundcheck" // libgpod: X = 1000 * 10 ^ (-.1 * Y) // where Y is the adjustment value in dB and X is the value that goes into the SoundCheck field return 30.0 - 10.0 * std::log10( m_track->soundcheck ); } void Track::setReplayGain( Meta::ReplayGainTag mode, qreal newReplayGain ) { guint32 soundcheck; switch( mode ) { case Meta::ReplayGain_Track_Gain: if( newReplayGain == 0.0 ) // libgpod: The value 0 is special, treated as "no Soundcheck" soundcheck = 0; else // libgpod: X = 1000 * 10 ^ (-.1 * Y) // where Y is the adjustment value in dB and X is the value that goes into the SoundCheck field soundcheck = 1000 * std::pow( 10.0, -0.1 * newReplayGain ); m_track->soundcheck = soundcheck; break; case Meta::ReplayGain_Album_Gain: case Meta::ReplayGain_Track_Peak: // we should somehow abuse Itdb_Track to store this, it is really needed case Meta::ReplayGain_Album_Peak: break; } } QString Track::type() const { QReadLocker locker( &m_trackLock ); return QString::fromUtf8( m_track->filetype ); } void Track::setType( const QString &newType ) { QWriteLocker locker( &m_trackLock ); g_free( m_track->filetype ); m_track->filetype = g_strdup( newType.toUtf8() ); commitIfInNonBatchUpdate( Meta::valFormat, newType ); } bool Track::inCollection() const { return m_coll; // converts to bool nicely } Collections::Collection* Track::collection() const { return m_coll.data(); } Meta::TrackEditorPtr Track::editor() { return Meta::TrackEditorPtr( isEditable() ? this : 0 ); } Meta::StatisticsPtr Track::statistics() { return Meta::StatisticsPtr( this ); } Meta::TrackPtr Track::fromIpodTrack( const Itdb_Track *ipodTrack ) { if( !ipodTrack ) return Meta::TrackPtr(); if( ipodTrack->usertype != m_gpodTrackUserTypeAmarokTrackPtr ) return Meta::TrackPtr(); if( !ipodTrack->userdata ) return Meta::TrackPtr(); return Meta::TrackPtr( static_cast( ipodTrack->userdata ) ); } Itdb_Track* Track::itdbTrack() const { return m_track; } bool Track::finalizeCopying( const gchar *mountPoint, const gchar *filePath ) { GError *error = 0; // we cannot use m_mountPoint, we are not yet in collection Itdb_Track *res = itdb_cp_finalize( m_track, mountPoint, filePath, &error ); if( error ) { warning() << "Failed to finalize copying of iPod track:" << error->message; g_error_free( error ); error = 0; } return res == m_track; } void Track::setCollection( QWeakPointer collection ) { m_coll = collection; if( !collection ) return; { // scope for locker QWriteLocker locker( &m_trackLock ); // paranoia: collection may become null while we were waiting for lock... m_mountPoint = collection ? collection.data()->mountPoint() : QString(); } // m_track->filetype field may have been set by someone else, rather check it (if set // by us, it can be more accurate than file extension, so we prefer it) if( !Amarok::FileTypeSupport::possibleFileTypes().contains( type() ) ) setType( Amarok::extension( playableUrl().path() ) ); // we don't make the datbase dirty, this can be recomputed every time } void Track::beginUpdate() { QWriteLocker locker( &m_trackLock ); m_batch++; } void Track::endUpdate() { QWriteLocker locker( &m_trackLock ); Q_ASSERT( m_batch > 0 ); m_batch--; commitIfInNonBatchUpdate(); } bool Track::isEditable() const { if( !inCollection() ) return false; return collection()->isWritable(); // IpodCollection implements this nicely } void Track::commitIfInNonBatchUpdate( qint64 field, const QVariant &value ) { m_changedFields.insert( field, value ); commitIfInNonBatchUpdate(); } void Track::commitIfInNonBatchUpdate() { static const QSet statFields = ( QSet() << Meta::valFirstPlayed << Meta::valLastPlayed << Meta::valPlaycount << Meta::valScore << Meta::valRating ); if( m_batch > 0 || m_changedFields.isEmpty() ) return; // we block changing the track meta-data of read-only iPod Collections; // it would only be cofusing to the user as the changes would get discarded. if( !m_coll || !m_coll.data()->isWritable() ) return; if( AmarokConfig::writeBackStatistics() || !(QSet::fromList( m_changedFields.keys() ) - statFields).isEmpty() ) { setModifyDate( QDateTime::currentDateTime() ); } m_trackLock.unlock(); // playableUrl() locks it too, notifyObservers() better without lock QString path = playableUrl().path(); // needs to be here because it locks m_trackLock too // write tags to file in a thread in order not to block WriteTagsJob *job = new WriteTagsJob( path, m_changedFields ); job->connect( job, SIGNAL(done(ThreadWeaver::Job*)), job, SLOT(deleteLater()) ); ThreadWeaver::Weaver::instance()->enqueue( job ); notifyObservers(); m_trackLock.lockForWrite(); // reset to original state when this was called m_changedFields.clear(); } // IpodMeta:Album Album::Album( Track *track ) : m_track( track ) { } QString Album::name() const { QReadLocker locker( &m_track->m_trackLock ); return QString::fromUtf8( m_track->m_track->album ); } bool Album::isCompilation() const { return m_track->m_track->compilation; } bool Album::canUpdateCompilation() const { Collections::Collection *coll = m_track->collection(); return coll ? coll->isWritable() : false; } void Album::setCompilation( bool isCompilation ) { m_track->setIsCompilation( isCompilation ); } bool Album::hasAlbumArtist() const { return !isCompilation(); } Meta::ArtistPtr Album::albumArtist() const { if( isCompilation() ) return Meta::ArtistPtr(); QReadLocker locker( &m_track->m_trackLock ); QString albumArtistName = QString::fromUtf8( m_track->m_track->albumartist ); if( albumArtistName.isEmpty() ) albumArtistName = QString::fromUtf8( m_track->m_track->artist ); return Meta::ArtistPtr( new Artist( albumArtistName ) ); } bool Album::hasImage( int size ) const { Q_UNUSED(size) if( m_track->m_track->has_artwork != 0x01 ) return false; // libgpod: has_artwork: True if set to 0x01, false if set to 0x02. return itdb_track_has_thumbnails( m_track->m_track ); // should be false if GdbPixBuf is not available } QImage Album::image( int size ) const { Q_UNUSED(size) // MemoryMeta does scaling for us QImage albumImage; #ifdef GDKPIXBUF_FOUND do { if( m_track->m_track->has_artwork != 0x01 ) break; // libgpod: has_artwork: True if set to 0x01, false if set to 0x02. // it reads "thumbnail", but this is the correct function to call GdkPixbuf *pixbuf = (GdkPixbuf*) itdb_track_get_thumbnail( m_track->m_track, -1, -1 ); if( !pixbuf ) break; if( gdk_pixbuf_get_colorspace( pixbuf ) != GDK_COLORSPACE_RGB ) { warning() << __PRETTY_FUNCTION__ << "Unsupported GTK colorspace."; g_object_unref( pixbuf ); break; } if( gdk_pixbuf_get_bits_per_sample( pixbuf ) != 8 ) { warning() << __PRETTY_FUNCTION__ << "Unsupported number of bits per sample."; g_object_unref( pixbuf ); break; } int n_channels = gdk_pixbuf_get_n_channels( pixbuf ); bool has_alpha = gdk_pixbuf_get_has_alpha( pixbuf ); QImage::Format format; if( n_channels == 4 && has_alpha ) format = QImage::Format_ARGB32; else if( n_channels == 3 && !has_alpha ) format = QImage::Format_RGB888; else { warning() << __PRETTY_FUNCTION__ << "Unsupported n_channels / has_alpha combination."; g_object_unref( pixbuf ); break; } // cost cast needed to choose QImage constructor that takes read-only image data albumImage = QImage( const_cast( gdk_pixbuf_get_pixels( pixbuf ) ), gdk_pixbuf_get_width( pixbuf ), gdk_pixbuf_get_height( pixbuf ), gdk_pixbuf_get_rowstride( pixbuf ), format ); // force deep copy so that memory from gdk pixbuf can be unreferenced: albumImage.setDotsPerMeterX( 2835 ); g_object_unref( pixbuf ); } while( false ); #endif return albumImage; } bool Album::canUpdateImage() const { #ifdef GDKPIXBUF_FOUND Collections::Collection *coll = m_track->collection(); return coll ? coll->isWritable() : false; #else return false; #endif } void Album::setImage( const QImage &image ) { m_track->setImage( image ); CoverCache::invalidateAlbum( this ); } void Album::removeImage() { setImage( QImage() ); } diff --git a/src/core-impl/collections/ipodcollection/IpodMeta.h b/src/core-impl/collections/ipodcollection/IpodMeta.h index cfa4f7f2a3..ea711dd88f 100644 --- a/src/core-impl/collections/ipodcollection/IpodMeta.h +++ b/src/core-impl/collections/ipodcollection/IpodMeta.h @@ -1,335 +1,335 @@ /**************************************************************************************** * Copyright (c) 2012 MatÄ›j Laitl . * ****************************************************************************************/ #ifndef IPODMETA_H #define IPODMETA_H #include "MetaValues.h" #include "core/meta/Meta.h" #include "core/meta/Statistics.h" #include "core/meta/TrackEditor.h" #include #include struct _Itdb_Track; typedef _Itdb_Track Itdb_Track; class IpodCollection; namespace IpodMeta { /** * An iPod track. album, artist, composer etc. are invisible to ouside world, they are * proxied in the MemoMeta track. All methods in this class are thread-safe with a few * exceptions that are noted in relevant method docstrings. */ class Track : public Meta::Track, public Meta::Statistics, public Meta::TrackEditor { public: /** * Constructs an iPod track from an existing libgpod track structure. Caller * must guarantee that these are already added to the collection's itdb databse. */ explicit Track( Itdb_Track *ipodTrack ); /** * Constructs an iPod track out of an existing track by copying its meta-data */ explicit Track( const Meta::TrackPtr &origTrack ); virtual ~Track(); // Meta::Base methods: virtual QString name() const; // Meta::Track methods: - virtual KUrl playableUrl() const; + virtual QUrl playableUrl() const; virtual QString prettyUrl() const; virtual QString uidUrl() const; virtual QString notPlayableReason() const; virtual Meta::AlbumPtr album() const; virtual Meta::ArtistPtr artist() const; virtual Meta::ComposerPtr composer() const; virtual Meta::GenrePtr genre() const; virtual Meta::YearPtr year() const; virtual qreal bpm() const; virtual QString comment() const; virtual qint64 length() const; virtual int filesize() const; virtual int sampleRate() const; virtual int bitrate() const; virtual QDateTime createDate() const; virtual QDateTime modifyDate() const; virtual int trackNumber() const; virtual int discNumber() const; virtual qreal replayGain( Meta::ReplayGainTag mode ) const; virtual QString type() const; virtual bool inCollection() const; virtual Collections::Collection* collection() const; virtual Meta::TrackEditorPtr editor(); virtual Meta::StatisticsPtr statistics(); // Meta::TrackEditor methods: virtual void setAlbum( const QString &newAlbum ); virtual void setAlbumArtist( const QString &newAlbumArtist ); virtual void setArtist( const QString &newArtist ); virtual void setComposer( const QString &newComposer ); virtual void setGenre( const QString &newGenre ); virtual void setYear( int newYear ); virtual void setTitle( const QString &newTitle ); virtual void setComment( const QString &newComment ); virtual void setTrackNumber( int newTrackNumber ); virtual void setDiscNumber( int newDiscNumber ); virtual void setBpm( const qreal newBpm ); // Meta::Statistics methods: virtual int rating() const; virtual void setRating( int newRating ); virtual QDateTime lastPlayed() const; virtual void setLastPlayed( const QDateTime &time ); virtual QDateTime firstPlayed() const; virtual void setFirstPlayed( const QDateTime &time ); virtual int playCount() const; virtual int recentPlayCount() const; virtual void setPlayCount( const int playcount ); // Combined Meta::TrackEditor, Meta::Statistics methods: virtual void beginUpdate(); virtual void endUpdate(); // IpodMeta::Track methods: /** * Return a pointer to IpodMeta::Track given pointer to underlying libgpod * track. Does not attempt to create the track, so it may return null ptr if * there is no IpodMeta::Track associated with given libgpod track. */ static Meta::TrackPtr fromIpodTrack( const Itdb_Track *ipodTrack ); /** * Return a pointer to underlying libgpod track. You aren't allowed to cache * the pointer - IpodMeta::Track owns it. Guaranteed to be non-null and * constant throughout the lifetime of IpodMeta::Track. */ Itdb_Track *itdbTrack() const; /** * CollectionLocation must call this method when it finishes copying the * track file onto iPod, before adding this track to IpodCollection. * Sets ipod_path, filetype_marker, transferred and size m_track fields. * * @param mountPoint a path where iPod is mounted, e.g. /media/MyiPod in local * encoding (use QFile::encodeName()) * @param filePath full absolute path to copied file, must be in form * <@param mountPoint>/iPod_Control/Music/... - it is recommended to use * itdb_cp_get_dest_filename() to construct the filename * * @return true if the track was "accepted", false if not in which case you * shouldn't add it to collection. */ bool finalizeCopying( const gchar *mountPoint, const gchar *filePath ); /** * Set collection this track belongs to. If collection is not null, (re)set * the mount point stored in track. (affects playableUrl()) */ void setCollection( QWeakPointer collection ); // Methods for copy constructor: void setIsCompilation( bool newIsCompilation ); void setImage( const QImage &newImage ); void setLength( qint64 newLength ); void setSampleRate( int newSampleRate ); void setBitrate( int newBitrate ); void setCreateDate( const QDateTime &newDate ); void setModifyDate( const QDateTime &newDate ); void setReplayGain( Meta::ReplayGainTag mode, qreal newReplayGain ); void setType( const QString &newType ); private: bool isEditable() const; /** * Must be called at end of every set*() method, with m_trackLock locked for * writing. Takes care of writing back the fields and notifying observers. */ void commitIfInNonBatchUpdate( qint64 field, const QVariant &value ); void commitIfInNonBatchUpdate(); friend class Album; // so that is can access m_track and friends /** * Meta::Track is memory-managed using KSharedPointer to QSharedData, but * IpodCollection's memory management is out of our control, therefore the * weak pointer. */ QWeakPointer m_coll; /** * While mount point is accessible through m_track->itdb-> ..., we want to * remember our location even when we are removed from collection */ QString m_mountPoint; /** * Associated libgpod track structure that holds all the data, we own this * pointer */ Itdb_Track *const m_track; // yes, the address is constant, not the track /** * You must hold this lock when acessing m_track data. Beware that * m_track->itdb may change even with this lock hold - IpodCollection is the * owner of this field */ mutable QReadWriteLock m_trackLock; /** * We need the temporary image file to exist for the lifetime of Track because * calling itdb_track_set_thumbnails() only saves the filename - the file is * read only when needed. If this path is non-empty, it means that the file * should be deleted in destructor. */ QString m_tempImageFilePath; /** * Set of field types (identified by constants from MetaValues.h) changed by * TrackEditor or set{Rating,Score,...} not yet committed to database and * underlying file */ Meta::FieldHash m_changedFields; /** * Number of current batch operations started by @see beginUpdate() and not * yet ended by @see endUpdate(). Must only be accessed with m_trackLock held. */ int m_batch; static const quint64 m_gpodTrackUserTypeAmarokTrackPtr = Q_UINT64_C(0x416d61726f6b5472); /* AmarokTr */ }; /** * Dummy Artist that just stores its name; not visible from outside - iPod tracks are * proxied by MemoryMeta that creates its own Artist entities. */ class Artist : public Meta::Artist { public: Artist( const QString &name ) : m_name( name ) {} virtual ~Artist() {} virtual QString name() const { return m_name; } virtual Meta::TrackList tracks() { return Meta::TrackList(); } private: QString m_name; }; /** * For performance reasons, Album stores just pointer to the tracks and reads all its * fields on-demand. */ class Album : public Meta::Album { public: Album( Track *track ); virtual QString name() const; // dummy, iPod tracks are supposed to be proxied by MemoryMeta which handles this virtual Meta::TrackList tracks() { return Meta::TrackList(); } virtual bool isCompilation() const; virtual bool canUpdateCompilation() const; virtual void setCompilation( bool isCompilation ); virtual bool hasAlbumArtist() const; virtual Meta::ArtistPtr albumArtist() const; virtual bool hasImage( int size = 0 ) const; virtual QImage image( int size = 0 ) const; virtual bool canUpdateImage() const; virtual void setImage( const QImage &image ); virtual void removeImage(); private: KSharedPtr m_track; }; /** * Dummy Composer that just stores its name; not visible from outside - iPod tracks are * proxied by MemoryMeta that creates its own Composer entities. */ class Composer : public Meta::Composer { public: Composer( const QString &name ) : m_name( name ) {} virtual ~Composer() {} virtual QString name() const { return m_name; } virtual Meta::TrackList tracks() { return Meta::TrackList(); } private: QString m_name; }; /** * Dummy Genre that just stores its name; not visible from outside - iPod tracks are * proxied by MemoryMeta that creates its own Genre entities. */ class Genre : public Meta::Genre { public: Genre( const QString &name ) : m_name( name ) {} virtual ~Genre() {} virtual QString name() const { return m_name; } virtual Meta::TrackList tracks() { return Meta::TrackList(); } private: QString m_name; }; /** * Dummy Year that just stores its name; not visible from outside - iPod tracks are * proxied by MemoryMeta that creates its own Year entities. */ class Year : public Meta::Year { public: Year( const QString &name ) : m_name( name ) {} virtual ~Year() {} virtual QString name() const { return m_name; } virtual Meta::TrackList tracks() { return Meta::TrackList(); } private: QString m_name; }; } // namespace IpodMeta #endif // IPODMETA_H diff --git a/src/core-impl/collections/ipodcollection/IpodPlaylist.cpp b/src/core-impl/collections/ipodcollection/IpodPlaylist.cpp index 61c60ba6c6..f68c8e8e21 100644 --- a/src/core-impl/collections/ipodcollection/IpodPlaylist.cpp +++ b/src/core-impl/collections/ipodcollection/IpodPlaylist.cpp @@ -1,248 +1,248 @@ /**************************************************************************************** * Copyright (c) 2012 MatÄ›j Laitl * * * * 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, see . * ****************************************************************************************/ #include "IpodPlaylist.h" #include "IpodCollection.h" #include "IpodMeta.h" #include "IpodPlaylistProvider.h" #include "core/playlists/PlaylistProvider.h" #include "core/support/Debug.h" #include "core-impl/collections/support/MemoryMeta.h" #include "core-impl/playlists/providers/user/UserPlaylistProvider.h" #include IpodPlaylist::IpodPlaylist( Itdb_Playlist *ipodPlaylist, IpodCollection *collection ) : m_playlist( ipodPlaylist ) , m_coll( collection ) , m_type( Normal ) { Q_ASSERT( m_playlist && collection ); for( GList *members = m_playlist->members; members; members = members->next ) { Itdb_Track *itdbTrack = (Itdb_Track *) members->data; Q_ASSERT( itdbTrack ); Meta::TrackPtr track = IpodMeta::Track::fromIpodTrack( itdbTrack ); Q_ASSERT( track ); track = collection->trackForUidUrl( track->uidUrl() ); // get MemoryMeta proxy track Q_ASSERT( track ); m_tracks << track; } } IpodPlaylist::IpodPlaylist( const Meta::TrackList &tracks, const QString &name, IpodCollection *collection, Type type ) : m_coll( collection ) , m_type( type ) { m_playlist = itdb_playlist_new( name.toUtf8(), false /* Smart playlist */ ); Q_ASSERT( m_playlist ); if( m_type != Normal ) { m_tracks = tracks; return; // m_playlist holds just the name in this case } int position = 0; int finalPosition = 0; foreach( Meta::TrackPtr track, tracks ) { if( track->collection() == collection ) // track from associated collection { addIpodTrack( track, position ); position++; } else m_tracksToCopy << TrackPosition( track, finalPosition ); finalPosition++; // yes increment every time, tracks are inserted in order so this is correct } if( !m_tracksToCopy.isEmpty() ) scheduleCopyAndInsert(); } IpodPlaylist::~IpodPlaylist() { itdb_playlist_free( m_playlist ); } -KUrl +QUrl IpodPlaylist::uidUrl() const { // integer reading is atomic, no lock needed QString collId = m_coll ? m_coll.data()->collectionId() : "removedipodcolleciton:/"; return QString( "%1/playlists/%2" ).arg( collId ).arg( m_playlist->id ); } QString IpodPlaylist::name() const { QReadLocker locker( &m_playlistLock ); return QString::fromUtf8( m_playlist->name ); } void IpodPlaylist::setName( const QString &name ) { QWriteLocker locker( &m_playlistLock ); g_free( m_playlist->name ); m_playlist->name = g_strdup( name.toUtf8() ); } Playlists::PlaylistProvider* IpodPlaylist::provider() const { return m_coll ? m_coll.data()->playlistProvider() : 0; } int IpodPlaylist::trackCount() const { return m_tracks.count(); } Meta::TrackList IpodPlaylist::tracks() { return m_tracks; } void IpodPlaylist::addTrack( Meta::TrackPtr track, int position ) { if( m_type != Normal || !m_coll || !m_coll.data()->isWritable() ) return; if( position < 0 || position > m_tracks.count() ) position = m_tracks.count(); if( track->collection() == m_coll.data() ) // track from associated collection addIpodTrack( track, position ); else { m_tracksToCopy << TrackPosition( track, position ); scheduleCopyAndInsert(); } } void IpodPlaylist::removeTrack( int position ) { // we should fail only if position is incorrect, prevent infinite loops in // IpodPlaylistProvider::removeTrackFromPlaylists() if( position < 0 || position >= m_tracks.count() ) return; Meta::TrackPtr removedTrack = m_tracks.takeAt( position ); if( m_type == Stale || m_type == Orphaned ) { notifyObserversTrackRemoved( position ); return; // do not fire following machinery for special playlists } KSharedPtr proxyTrack = KSharedPtr::dynamicCast( removedTrack ); if( !proxyTrack ) { error() << __PRETTY_FUNCTION__ << "track" << removedTrack.data() << "from m_track was not MemoryMeta track!"; return; } KSharedPtr ipodTrack = KSharedPtr::dynamicCast( proxyTrack->originalTrack() ); if( !proxyTrack ) { error() << __PRETTY_FUNCTION__ << "originalTrack of the proxyTrack was not IpodMeta track!"; return; } { // notify observers _without_ the lock held QWriteLocker locker( &m_playlistLock ); itdb_playlist_remove_track( m_playlist, ipodTrack->itdbTrack() ); } notifyObserversTrackRemoved( position ); } Itdb_Playlist* IpodPlaylist::itdbPlaylist() { return m_playlist; } TrackPositionList IpodPlaylist::takeTracksToCopy() { TrackPositionList tracksToCopy = m_tracksToCopy; m_tracksToCopy.clear(); return tracksToCopy; } void IpodPlaylist::scheduleCopyAndInsert() { Playlists::PlaylistProvider *prov = provider(); if( !prov ) return; // we can do nothing static_cast( prov )->scheduleCopyAndInsertToPlaylist( KSharedPtr( this ) ); } void IpodPlaylist::addIpodTrack( Meta::TrackPtr track, int position ) { Q_ASSERT( position >= 0 && position <= m_tracks.count() ); Meta::TrackPtr proxyTrack = Meta::TrackPtr(); KSharedPtr memoryTrack = KSharedPtr::dynamicCast( track ); if( memoryTrack ) { track = memoryTrack->originalTrack(); // iPod track is usually hidden below MemoryMeta proxy proxyTrack = track; } KSharedPtr ipodTrack = KSharedPtr::dynamicCast( track ); if( !ipodTrack ) { error() << __PRETTY_FUNCTION__ << "Could not get IpodMeta::Track out of supplied track." << ( memoryTrack ? "(but cast to MemoryMeta::Track succeeded)" : "(cast to MemoryMeta::Track failed too)" ); return; } if( !proxyTrack) // we got IpodTrack directly, expose its MemoryMeta proxy proxyTrack = m_coll ? m_coll.data()->trackForUidUrl( ipodTrack->uidUrl() ) : Meta::TrackPtr(); if( !proxyTrack ) { error() << __PRETTY_FUNCTION__ << "was passed IpodMeta::Track but we could not find" << "MemoryMeta::Track proxy for it."; return; } Itdb_Track *itdbTrack = ipodTrack->itdbTrack(); /* There is following code in libgpod's itdb_playlist_add_track(): * g_return_if_fail (pl->itdb); * track->itdb = pl->itdb; * Just fool libgpod by setting itdb to assumed value */ Itdb_iTunesDB *save = m_playlist->itdb; m_playlist->itdb = itdbTrack->itdb; itdb_playlist_add_track( m_playlist, itdbTrack, -1 ); m_playlist->itdb = save; m_tracks.insert( position, proxyTrack ); notifyObserversTrackAdded( proxyTrack, position ); } diff --git a/src/core-impl/collections/ipodcollection/IpodPlaylist.h b/src/core-impl/collections/ipodcollection/IpodPlaylist.h index eabae04bfd..28dd7fc917 100644 --- a/src/core-impl/collections/ipodcollection/IpodPlaylist.h +++ b/src/core-impl/collections/ipodcollection/IpodPlaylist.h @@ -1,112 +1,112 @@ /**************************************************************************************** * Copyright (c) 2012 MatÄ›j Laitl * * * * 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, see . * ****************************************************************************************/ #ifndef IPODPLAYLIST_H #define IPODPLAYLIST_H #include "core/playlists/Playlist.h" #include class IpodCollection; struct _Itdb_Playlist; typedef struct _Itdb_Playlist Itdb_Playlist; // we cannot use QMap because it doesn't preserve order typedef QPair TrackPosition; typedef QList TrackPositionList; /** * Represents playlist on the iPod. Takes ownership of the m_playlist pointer. */ class IpodPlaylist : public Playlists::Playlist { public: enum Type { Normal, // regular iPod playlist Stale, // playlist containing stale iTunes database entries Orphaned, // playlist containing track on iPod filesystem that are not it database }; /** * Create Amarok iPod playlist out of existing itdb playlist */ IpodPlaylist( Itdb_Playlist *ipodPlaylist, IpodCollection *collection ); /** * Create new Amarok iPod playlist. Some @param tracks may not be in corresponding * iPod collection, these are copied to iPod (unless not matched by meta tags) * * @param type whether this playlist is an ordinatory one or a kind of special */ IpodPlaylist( const Meta::TrackList &tracks, const QString &name, IpodCollection *collection, Type type = Normal ); virtual ~IpodPlaylist(); - virtual KUrl uidUrl() const; + virtual QUrl uidUrl() const; virtual QString name() const; virtual void setName( const QString &name ); virtual Playlists::PlaylistProvider *provider() const; virtual int trackCount() const; virtual Meta::TrackList tracks(); virtual void addTrack( Meta::TrackPtr track, int position = -1 ); virtual void removeTrack( int position ); // IpodPlaylist specific: Itdb_Playlist *itdbPlaylist(); /** * Return tracks that should be copied to iPod and then added to this playlist, * clearing this map. */ TrackPositionList takeTracksToCopy(); /** * Return type of this playlist. Can be used to determine whether this one is * special or not. */ Type type() const { return m_type; } private: Q_DISABLE_COPY( IpodPlaylist ) /** * Copy m_tracksToCopy to iPod and them add them to this playlist. The actual call * to start copying tracks is deferred to next eventloop iteration to pickup * multiple successive addTrack() calls */ void scheduleCopyAndInsert(); /** * Does the dirty job of adding @param track to this playlist, both to m_tracks * and to underlying libgpoid playlist. @param position must be a valid position * otherwise this method asserts out. */ void addIpodTrack( Meta::TrackPtr track, int position ); Itdb_Playlist *m_playlist; mutable QReadWriteLock m_playlistLock; QWeakPointer m_coll; Type m_type; Meta::TrackList m_tracks; // playlists tracks, in fact MemoryMeta::Track objects TrackPositionList m_tracksToCopy; }; #endif // IPODPLAYLIST_H diff --git a/src/core-impl/collections/ipodcollection/jobs/IpodCopyTracksJob.cpp b/src/core-impl/collections/ipodcollection/jobs/IpodCopyTracksJob.cpp index a8c527b43c..5961b95b83 100644 --- a/src/core-impl/collections/ipodcollection/jobs/IpodCopyTracksJob.cpp +++ b/src/core-impl/collections/ipodcollection/jobs/IpodCopyTracksJob.cpp @@ -1,412 +1,412 @@ /**************************************************************************************** * Copyright (c) 2012 MatÄ›j Laitl * * * * 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, see . * ****************************************************************************************/ #include "IpodCopyTracksJob.h" #include "IpodMeta.h" #include "core/collections/QueryMaker.h" #include "core/interfaces/Logger.h" #include "core/support/Components.h" #include "core/support/Debug.h" #include "core/transcoding/TranscodingController.h" #include "MetaTagLib.h" #include "FileType.h" #include "transcoding/TranscodingJob.h" #include #include #include #include #include #include // fsync() -IpodCopyTracksJob::IpodCopyTracksJob( const QMap &sources, +IpodCopyTracksJob::IpodCopyTracksJob( const QMap &sources, const QWeakPointer &collection, const Transcoding::Configuration &configuration, bool goingToRemoveSources ) : Job() , m_coll( collection ) , m_transcodingConfig( configuration ) , m_sources( sources ) , m_aborted( false ) , m_goingToRemoveSources( goingToRemoveSources ) { connect( this, SIGNAL(startDuplicateTrackSearch(Meta::TrackPtr)), SLOT(slotStartDuplicateTrackSearch(Meta::TrackPtr)) ); - connect( this, SIGNAL(startCopyOrTranscodeJob(KUrl,KUrl,bool)), - SLOT(slotStartCopyOrTranscodeJob(KUrl,KUrl,bool)) ); + connect( this, SIGNAL(startCopyOrTranscodeJob(QUrl,QUrl,bool)), + SLOT(slotStartCopyOrTranscodeJob(QUrl,QUrl,bool)) ); connect( this, SIGNAL(displaySorryDialog()), SLOT(slotDisplaySorryDialog()) ); } void IpodCopyTracksJob::run() { if( !m_coll ) return; // destructed behind our back float totalSafeCapacity = m_coll.data()->totalCapacity() - m_coll.data()->capacityMargin(); QByteArray mountPoint = QFile::encodeName( m_coll.data()->mountPoint() ); QString collectionPrettyName = m_coll.data()->prettyName(); itdb_start_sync( m_coll.data()->m_itdb ); - QMapIterator it( m_sources ); + QMapIterator it( m_sources ); while( it.hasNext() ) { if( m_aborted || !m_coll ) break; it.next(); Meta::TrackPtr track = it.key(); - KUrl sourceUrl = it.value(); + QUrl sourceUrl = it.value(); emit startDuplicateTrackSearch( track ); // wait for searching to finish: m_searchingForDuplicates.acquire( 1 ); if( m_duplicateTrack ) { trackProcessed( Duplicate, track, m_duplicateTrack ); continue; } if( !m_coll ) break; // destructed behind our back bool isJustCopy = m_transcodingConfig.isJustCopy( track, m_coll.data()->supportedFormats() ); if( isJustCopy // if not copying, we catch big files later && track->filesize() > totalSafeCapacity - m_coll.data()->usedCapacity() ) { // this is a best effort check, we do one definite one after the file is copied debug() << "Refusing to copy" << track->prettyUrl() << "to iPod: there are only" << totalSafeCapacity - m_coll.data()->usedCapacity() << "free bytes (not" << "counting a safety margin) on iPod and track has" << track->filesize() << "bytes."; trackProcessed( ExceededingSafeCapacity, track ); continue; } QString fileExtension; if( isJustCopy ) fileExtension = track->type(); else fileExtension = Amarok::Components::transcodingController()->format( m_transcodingConfig.encoder() )->fileExtension(); if( !m_coll.data()->supportedFormats().contains( fileExtension ) ) { m_notPlayableFormats.insert( fileExtension ); trackProcessed( NotPlayable, track ); continue; } QByteArray fakeSrcName( "filename." ); // only for file extension fakeSrcName.append( QFile::encodeName( fileExtension ) ); /* determine destination filename; we cannot use ipodTrack because as it has no itdb * (and thus mountpoint) set */ GError *error = 0; gchar *destFilename = itdb_cp_get_dest_filename( 0, mountPoint, fakeSrcName, &error ); if( error ) { warning() << "Cannot construct iPod track filename:" << error->message; g_error_free( error ); error = 0; } if( !destFilename ) { trackProcessed( InternalError, track ); continue; } // start the physical copying - KUrl destUrl = KUrl( QFile::decodeName( destFilename ) ); + QUrl destUrl = QUrl( QFile::decodeName( destFilename ) ); emit startCopyOrTranscodeJob( sourceUrl, destUrl, isJustCopy ); // wait for copying to finish: m_copying.acquire( 1 ); /* fsync so that progress bar gives correct info and user doesn't remove the iPod * prematurely */ QFile destFile( QFile::decodeName( destFilename ) ); if( !destFile.exists() ) { debug() << destFile.fileName() << "does not exist even though we thought we copied it to iPod"; trackProcessed( CopyingFailed, track ); continue; } if( !m_coll ) break; // destructed behind our back if( m_coll.data()->usedCapacity() > totalSafeCapacity ) { debug() << "We exceeded total safe-to-use capacity on iPod (safe-to-use:" << totalSafeCapacity << "B, used:" << m_coll.data()->usedCapacity() << "): removing copied track from iPod"; destFile.remove(); trackProcessed( ExceededingSafeCapacity, track ); continue; } // fsync needs a file opened for writing, and without Apped it truncates files (?) if( !destFile.open( QIODevice::WriteOnly | QIODevice::Append ) ) { warning() << "Cannot open file copied to ipod (for writing):" << destFile.fileName() << ": removing it"; destFile.remove(); trackProcessed( InternalError, track ); continue; } if( destFile.size() ) fsync( destFile.handle() ); // should flush all kernel buffers to disk destFile.close(); // create a new track object by copying meta-data from existing one: IpodMeta::Track *ipodTrack = new IpodMeta::Track( track ); // tell the track it has been copied: bool accepted = ipodTrack->finalizeCopying( mountPoint, destFilename ); g_free( destFilename ); destFilename = 0; if( !accepted ) { debug() << "ipodTrack->finalizeCopying( destFilename ) returned false!"; delete ipodTrack; trackProcessed( InternalError, track ); continue; } if( !isJustCopy ) { // we need to reread some metadata in case the file was transcoded Meta::FieldHash fields = Meta::Tag::readTags( destFile.fileName() ); ipodTrack->setBitrate( fields.value( Meta::valBitrate, 0 ).toInt() ); ipodTrack->setLength( fields.value( Meta::valLength, 0 ).toLongLong() ); ipodTrack->setSampleRate( fields.value( Meta::valSamplerate, 0 ).toInt() ); Amarok::FileType type = Amarok::FileType( fields.value( Meta::valFormat, 0 ).toInt() ); ipodTrack->setType( Amarok::FileTypeSupport::toString( type ) ); // we retain ReplayGain, tags etc - these shouldn't change; size is read // in finalizeCopying() } // add the track to collection if( !m_coll ) { delete ipodTrack; break; // we were waiting for copying, m_coll may got destoryed } Meta::TrackPtr newTrack = m_coll.data()->addTrack( ipodTrack ); if( !newTrack ) { destFile.remove(); trackProcessed( InternalError, track ); continue; } trackProcessed( Success, track, newTrack ); } if( m_coll ) itdb_stop_sync( m_coll.data()->m_itdb ); emit endProgressOperation( this ); int sourceSize = m_sources.size(); int successCount = m_sourceTrackStatus.count( Success ); int duplicateCount = m_sourceTrackStatus.count( Duplicate ); QString transferredText; if ( m_transcodingConfig.isJustCopy() ) transferredText = i18ncp( "%2 is collection name", "Transferred one track to %2.", "Transferred %1 tracks to %2.", successCount, collectionPrettyName ); else transferredText = i18ncp( "%2 is collection name", "Transcoded one track to %2.", "Transcoded %1 tracks to %2.", successCount, collectionPrettyName ); if( successCount == sourceSize ) { Amarok::Components::logger()->shortMessage( transferredText ); } else if( m_aborted ) { QString text = i18np( "Transfer aborted. Managed to transfer one track.", "Transfer aborted. Managed to transfer %1 tracks.", successCount ); Amarok::Components::logger()->longMessage( text ); } else if( successCount + duplicateCount == sourceSize ) { QString text = i18ncp( "%2 is the 'Transferred 123 tracks to Some collection.' message", "%2 One track was already there.", "%2 %1 tracks were already there.", duplicateCount, transferredText ); Amarok::Components::logger()->longMessage( text ); } else { // somethig more severe failed, notify user using a dialog emit displaySorryDialog(); } } void IpodCopyTracksJob::abort() { m_aborted = true; } void IpodCopyTracksJob::slotStartDuplicateTrackSearch( const Meta::TrackPtr &track ) { Collections::QueryMaker *qm = m_coll.data()->queryMaker(); qm->setQueryType( Collections::QueryMaker::Track ); // we cannot qm->addMatch( track ) - it matches by uidUrl() qm->addFilter( Meta::valTitle, track->name(), true, true ); qm->addMatch( track->album() ); qm->addMatch( track->artist(), Collections::QueryMaker::TrackArtists ); qm->addMatch( track->composer() ); qm->addMatch( track->year() ); qm->addNumberFilter( Meta::valTrackNr, track->trackNumber(), Collections::QueryMaker::Equals ); qm->addNumberFilter( Meta::valDiscNr, track->discNumber(), Collections::QueryMaker::Equals ); // we don't want to match by filesize, track length, filetype etc - these change during // transcoding. We don't match album artist because handling of it is inconsistent connect( qm, SIGNAL(newResultReady(Meta::TrackList)), SLOT(slotDuplicateTrackSearchNewResult(Meta::TrackList)) ); connect( qm, SIGNAL(queryDone()), SLOT(slotDuplicateTrackSearchQueryDone()) ); qm->setAutoDelete( true ); m_duplicateTrack = Meta::TrackPtr(); // reset duplicate track from previous query qm->run(); } void IpodCopyTracksJob::slotDuplicateTrackSearchNewResult( const Meta::TrackList &tracks ) { if( !tracks.isEmpty() ) // we don't really know which one, but be sure to allow multiple results m_duplicateTrack = tracks.last(); } void IpodCopyTracksJob::slotDuplicateTrackSearchQueryDone() { m_searchingForDuplicates.release( 1 ); // wakeup run() } void -IpodCopyTracksJob::slotStartCopyOrTranscodeJob( const KUrl &sourceUrl, const KUrl &destUrl, +IpodCopyTracksJob::slotStartCopyOrTranscodeJob( const QUrl &sourceUrl, const QUrl &destUrl, bool isJustCopy ) { KJob *job = 0; if( isJustCopy ) { if( m_goingToRemoveSources && m_coll && sourceUrl.toLocalFile().startsWith( m_coll.data()->mountPoint() ) ) { // special case for "add orphaned tracks" to either save space and significantly // speed-up the process: debug() << "Moving from" << sourceUrl << "to" << destUrl; job = KIO::file_move( sourceUrl, destUrl, -1, KIO::HideProgressInfo | KIO::Overwrite ); } else { debug() << "Copying from" << sourceUrl << "to" << destUrl; job = KIO::file_copy( sourceUrl, destUrl, -1, KIO::HideProgressInfo | KIO::Overwrite ); } } else { debug() << "Transcoding from" << sourceUrl << "to" << destUrl; job = new Transcoding::Job( sourceUrl, destUrl, m_transcodingConfig ); } job->setUiDelegate( 0 ); // be non-interactive connect( job, SIGNAL(finished(KJob*)), // we must use this instead of result() to prevent deadlock SLOT(slotCopyOrTranscodeJobFinished(KJob*)) ); job->start(); // no-op for KIO job, but matters for transcoding job } void IpodCopyTracksJob::slotCopyOrTranscodeJobFinished( KJob *job ) { if( job->error() != 0 && m_copyErrors.count() < 10 ) m_copyErrors.insert( job->errorString() ); m_copying.release( 1 ); // wakeup run() } void IpodCopyTracksJob::slotDisplaySorryDialog() { int sourceSize = m_sources.size(); int successCount = m_sourceTrackStatus.count( Success ); // match string with IpodCollectionLocation::prettyLocation() QString collName = m_coll ? m_coll.data()->prettyName() : i18n( "Disconnected iPod/iPad/iPhone" ); QString caption = i18nc( "%1 is collection pretty name, e.g. My Little iPod", "Transferred Tracks to %1", collName ); QString text; if( successCount ) text = i18np( "One track successfully transferred, but transfer of some other tracks failed.", "%1 tracks successfully transferred, but transfer of some other tracks failed.", successCount ); else text = i18n( "Transfer of tracks failed." ); QString details; int exceededingSafeCapacityCount = m_sourceTrackStatus.count( ExceededingSafeCapacity ); if( exceededingSafeCapacityCount ) { details += i18np( "One track was not transferred because it would exceed iPod capacity.
", "%1 tracks were not transferred because it would exceed iPod capacity.
", exceededingSafeCapacityCount ); QString reservedSpace = m_coll ? KGlobal::locale()->formatByteSize( m_coll.data()->capacityMargin(), 1 ) : QString( "???" ); // improbable, don't bother translators details += i18nc( "Example of %1 would be: 20.0 MiB", "Amarok reserves %1 on iPod for iTunes database writing.
", reservedSpace ); } int notPlayableCount = m_sourceTrackStatus.count( NotPlayable ); if( notPlayableCount ) { QString formats = QStringList( m_notPlayableFormats.toList() ).join( ", " ); details += i18np( "One track was not copied because it wouldn't be playable - its " " %2 format is unsupported.
", "%1 tracks were not copied because they wouldn't be playable - " "they are in unsupported formats (%2).
", notPlayableCount, formats ); } int copyingFailedCount = m_sourceTrackStatus.count( CopyingFailed ); if( copyingFailedCount ) { details += i18np( "Copy/move/transcode of one file failed.
", "Copy/move/transcode of %1 files failed.
", copyingFailedCount ); } int internalErrorCount = m_sourceTrackStatus.count( InternalError ); if( internalErrorCount ) { details += i18np( "One track was not transferred due to an internal Amarok error.
", "%1 tracks were not transferred due to an internal Amarok error.
", internalErrorCount ); details += i18n( "You can find details in Amarok debugging output.
" ); } if( m_sourceTrackStatus.size() != sourceSize ) { // aborted case was already caught in run() details += i18n( "The rest was not transferred because iPod collection disappeared.
" ); } if( !m_copyErrors.isEmpty() ) { details += i18nc( "%1 is a list of errors that occurred during copying of tracks", "Error causes: %1
", QStringList( m_copyErrors.toList() ).join( "
" ) ); } KMessageBox::detailedSorry( 0, text, details, caption ); } void IpodCopyTracksJob::trackProcessed( CopiedStatus status, Meta::TrackPtr srcTrack, Meta::TrackPtr destTrack ) { m_sourceTrackStatus.insert( status, srcTrack ); emit incrementProgress(); emit signalTrackProcessed( srcTrack, destTrack, status ); } diff --git a/src/core-impl/collections/ipodcollection/jobs/IpodCopyTracksJob.h b/src/core-impl/collections/ipodcollection/jobs/IpodCopyTracksJob.h index 0258bf2981..90071fb3b1 100644 --- a/src/core-impl/collections/ipodcollection/jobs/IpodCopyTracksJob.h +++ b/src/core-impl/collections/ipodcollection/jobs/IpodCopyTracksJob.h @@ -1,111 +1,111 @@ /**************************************************************************************** * Copyright (c) 2012 MatÄ›j Laitl * * * * 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, see . * ****************************************************************************************/ #ifndef COPYTRACKSJOB_H #define COPYTRACKSJOB_H #include "IpodCollection.h" #include "core/meta/forward_declarations.h" #include "core/transcoding/TranscodingConfiguration.h" #include #include #include class KJob; class IpodCopyTracksJob : public ThreadWeaver::Job { Q_OBJECT public: enum CopiedStatus { Duplicate, /// a track with same meta-data is already in iPod collection ExceededingSafeCapacity, /// would exceed "safe" capacity NotPlayable, /// track format would not be playable on connected iPod CopyingFailed, /// KIO failed to copy the file InternalError, /// all other reasons that have no nice user-tellable reason Success /// copied successfully }; /** * @param goingToRemoveSources whether this is in fact a move operation */ - IpodCopyTracksJob( const QMap &sources, + IpodCopyTracksJob( const QMap &sources, const QWeakPointer &collection, const Transcoding::Configuration &configuration, bool goingToRemoveSources ); virtual void run(); public slots: void abort(); signals: // a hack to create QueryMaken in a thread with event loop: void startDuplicateTrackSearch( const Meta::TrackPtr &track ); // a hack to create copyjob in a thread with event loop: - void startCopyOrTranscodeJob( const KUrl &src, const KUrl &dest, bool isJustCopy ); + void startCopyOrTranscodeJob( const QUrl &src, const QUrl &dest, bool isJustCopy ); // a hack to display KMessageBox in a gui thread: void displaySorryDialog(); // signals for progress operation: void incrementProgress(); void endProgressOperation( QObject *obj ); void totalSteps( int steps ); // not used, defined to keep QObject::conect warning quiet /** * Signal various track copy statuses back to IpodCollectionLocation * @param srcTrack source track, always non-nul * @param destTrack destination track on iPod, copied one or existing if * status == Duplicate; may be null * @param status copying status */ void signalTrackProcessed( Meta::TrackPtr srcTrack, Meta::TrackPtr destTrack, IpodCopyTracksJob::CopiedStatus status ); private slots: /// @see startDuplicateTrackSearch() void slotStartDuplicateTrackSearch( const Meta::TrackPtr &track ); void slotDuplicateTrackSearchNewResult( const Meta::TrackList &tracks ); void slotDuplicateTrackSearchQueryDone(); /// @see startCopyJob() - void slotStartCopyOrTranscodeJob( const KUrl &sourceUrl, const KUrl &destUrl, + void slotStartCopyOrTranscodeJob( const QUrl &sourceUrl, const QUrl &destUrl, bool isJustCopy ); void slotCopyOrTranscodeJobFinished( KJob *job ); /// @see displaySorryDialog() void slotDisplaySorryDialog(); private: void trackProcessed( CopiedStatus status, Meta::TrackPtr srcTrack, Meta::TrackPtr destTrack = Meta::TrackPtr() ); QWeakPointer m_coll; Transcoding::Configuration m_transcodingConfig; - QMap m_sources; + QMap m_sources; QMultiHash m_sourceTrackStatus; QSemaphore m_copying; QSemaphore m_searchingForDuplicates; Meta::TrackPtr m_duplicateTrack; bool m_aborted; bool m_goingToRemoveSources; QSet m_notPlayableFormats; QSet m_copyErrors; }; #endif // COPYTRACKSJOB_H diff --git a/src/core-impl/collections/ipodcollection/jobs/IpodParseTracksJob.cpp b/src/core-impl/collections/ipodcollection/jobs/IpodParseTracksJob.cpp index ad02410aac..39d3a5b02a 100644 --- a/src/core-impl/collections/ipodcollection/jobs/IpodParseTracksJob.cpp +++ b/src/core-impl/collections/ipodcollection/jobs/IpodParseTracksJob.cpp @@ -1,164 +1,164 @@ /**************************************************************************************** * Copyright (c) 2012 MatÄ›j Laitl * * * * 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, see . * ****************************************************************************************/ #include "IpodParseTracksJob.h" #include "IpodCollection.h" #include "IpodMeta.h" #include "IpodPlaylistProvider.h" #include "core/interfaces/Logger.h" #include "core/support/Components.h" #include "core/support/Debug.h" #include "core-impl/meta/file/File.h" #include #include #include IpodParseTracksJob::IpodParseTracksJob( IpodCollection *collection ) : Job() , m_coll( collection ) , m_aborted( false ) { } void IpodParseTracksJob::abort() { m_aborted = true; } void IpodParseTracksJob::run() { DEBUG_BLOCK Itdb_iTunesDB *itdb = m_coll->m_itdb; if( !itdb ) return; // paranoia guint32 trackNumber = itdb_tracks_number( itdb ); QString operationText = i18nc( "operation when iPod is connected", "Reading iPod tracks" ); Amarok::Components::logger()->newProgressOperation( this, operationText, trackNumber, this, SLOT(abort()) ); Meta::TrackList staleTracks; QSet knownPaths; for( GList *tracklist = itdb->tracks; tracklist; tracklist = tracklist->next ) { if( m_aborted ) break; Itdb_Track *ipodTrack = (Itdb_Track *) tracklist->data; if( !ipodTrack ) continue; // paranoia // IpodCollection::addTrack() locks and unlocks the m_itdbMutex mutex Meta::TrackPtr proxyTrack = m_coll->addTrack( new IpodMeta::Track( ipodTrack ) ); if( proxyTrack ) { QString canonPath = QFileInfo( proxyTrack->playableUrl().toLocalFile() ).canonicalFilePath(); if( !proxyTrack->isPlayable() ) staleTracks.append( proxyTrack ); else if( !canonPath.isEmpty() ) // nonexistent files return empty canonical path knownPaths.insert( canonPath ); } incrementProgress(); } parsePlaylists( staleTracks, knownPaths ); emit endProgressOperation( this ); } void IpodParseTracksJob::parsePlaylists( const Meta::TrackList &staleTracks, const QSet &knownPaths ) { IpodPlaylistProvider *prov = m_coll->m_playlistProvider; if( !prov || m_aborted ) return; if( !staleTracks.isEmpty() ) { prov->m_stalePlaylist = Playlists::PlaylistPtr( new IpodPlaylist( staleTracks, i18nc( "iPod playlist name", "Stale tracks" ), m_coll, IpodPlaylist::Stale ) ); prov->m_playlists << prov->m_stalePlaylist; // we don't subscribe to this playlist, no need to update database emit prov->playlistAdded( prov->m_stalePlaylist ); } Meta::TrackList orphanedTracks = findOrphanedTracks( knownPaths ); if( !orphanedTracks.isEmpty() ) { prov->m_orphanedPlaylist = Playlists::PlaylistPtr( new IpodPlaylist( orphanedTracks, i18nc( "iPod playlist name", "Orphaned tracks" ), m_coll, IpodPlaylist::Orphaned ) ); prov->m_playlists << prov->m_orphanedPlaylist; // we don't subscribe to this playlist, no need to update database emit prov->playlistAdded( prov->m_orphanedPlaylist ); } if( !m_coll->m_itdb || m_aborted ) return; for( GList *playlists = m_coll->m_itdb->playlists; playlists; playlists = playlists->next ) { Itdb_Playlist *playlist = (Itdb_Playlist *) playlists->data; if( !playlist || itdb_playlist_is_mpl( playlist ) ) continue; // skip null (?) or master playlists Playlists::PlaylistPtr playlistPtr( new IpodPlaylist( playlist, m_coll ) ); prov->m_playlists << playlistPtr; prov->subscribeTo( playlistPtr ); emit prov->playlistAdded( playlistPtr ); } if( !m_aborted && ( prov->m_stalePlaylist || prov->m_orphanedPlaylist ) ) { QString text = i18n( "Stale and/or orphaned tracks detected on %1. You can resolve " "the situation using the %2 collection action. You can also view the tracks " "under the Saved Playlists tab.", m_coll->prettyName(), m_coll->m_consolidateAction->text() ); Amarok::Components::logger()->longMessage( text ); } } Meta::TrackList IpodParseTracksJob::findOrphanedTracks(const QSet< QString >& knownPaths) { gchar *musicDirChar = itdb_get_music_dir( QFile::encodeName( m_coll->mountPoint() ) ); QString musicDirPath = QFile::decodeName( musicDirChar ); g_free( musicDirChar ); musicDirChar = 0; QStringList trackPatterns; foreach( const QString &suffix, m_coll->supportedFormats() ) { trackPatterns << QString( "*.%1" ).arg( suffix ); } Meta::TrackList orphanedTracks; QDir musicDir( musicDirPath ); foreach( QString subdir, musicDir.entryList( QStringList( "F??" ), QDir::Dirs | QDir::NoDotAndDotDot ) ) { if( m_aborted ) return Meta::TrackList(); subdir = musicDir.absoluteFilePath( subdir ); // make the path absolute foreach( const QFileInfo &info, QDir( subdir ).entryInfoList( trackPatterns ) ) { QString canonPath = info.canonicalFilePath(); if( knownPaths.contains( canonPath ) ) continue; // already in iTunes database - Meta::TrackPtr track( new MetaFile::Track( KUrl( canonPath ) ) ); + Meta::TrackPtr track( new MetaFile::Track( QUrl( canonPath ) ) ); orphanedTracks << track; } } return orphanedTracks; } diff --git a/src/core-impl/collections/mediadevicecollection/MediaDeviceCollection.h b/src/core-impl/collections/mediadevicecollection/MediaDeviceCollection.h index 5618e09522..5e87862a39 100644 --- a/src/core-impl/collections/mediadevicecollection/MediaDeviceCollection.h +++ b/src/core-impl/collections/mediadevicecollection/MediaDeviceCollection.h @@ -1,174 +1,174 @@ /**************************************************************************************** * Copyright (c) 2008 Alejandro Wainzinger * * * * 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, see . * ****************************************************************************************/ #ifndef MEDIADEVICECOLLECTION_H #define MEDIADEVICECOLLECTION_H #include "core/collections/Collection.h" #include "core-impl/collections/mediadevicecollection/MediaDeviceCollectionLocation.h" #include "core-impl/collections/mediadevicecollection/support/ConnectionAssistant.h" #include "core-impl/collections/mediadevicecollection/support/mediadevicecollection_export.h" #include "core-impl/collections/support/MemoryCollection.h" #include #include #include namespace Collections { class MediaDeviceCollection; /** * HACK: Base and Factory are separate because Q_OBJECT does not work directly with templates. * Templates used to reduce duplicated code in subclasses. */ class MEDIADEVICECOLLECTION_EXPORT MediaDeviceCollectionFactoryBase : public Collections::CollectionFactory { Q_OBJECT public: virtual ~MediaDeviceCollectionFactoryBase(); virtual void init(); protected: MediaDeviceCollectionFactoryBase( QObject *parent, const QVariantList &args, ConnectionAssistant* assistant ); protected slots: virtual void slotDeviceDetected( MediaDeviceInfo* info ); // detected type of device, connect it private slots: void slotDeviceDisconnected( const QString &udi ); private: virtual MediaDeviceCollection* createCollection( MediaDeviceInfo* info ) = 0; ConnectionAssistant* m_assistant; QMap m_collectionMap; }; template class MediaDeviceCollectionFactory : public MediaDeviceCollectionFactoryBase { protected: MediaDeviceCollectionFactory( QObject *parent, const QVariantList &args, ConnectionAssistant *assistant ) : MediaDeviceCollectionFactoryBase( parent, args, assistant ) {} virtual ~MediaDeviceCollectionFactory() {} private: virtual MediaDeviceCollection* createCollection( MediaDeviceInfo* info ) { return new CollType( info ); } }; class MEDIADEVICECOLLECTION_EXPORT MediaDeviceCollection : public Collections::Collection { Q_OBJECT public: /** Collection-related methods */ virtual ~MediaDeviceCollection(); /** * url-based methods can be abstracted via use of Amarok URLs * subclasses simply define a protocol prefix, e.g. ipod */ - virtual bool possiblyContainsTrack( const KUrl &url ) const { Q_UNUSED(url); return false;} // TODO: NYI - virtual Meta::TrackPtr trackForUrl( const KUrl &url ) { Q_UNUSED(url); return Meta::TrackPtr(); } // TODO: NYI + virtual bool possiblyContainsTrack( const QUrl &url ) const { Q_UNUSED(url); return false;} // TODO: NYI + virtual Meta::TrackPtr trackForUrl( const QUrl &url ) { Q_UNUSED(url); return Meta::TrackPtr(); } // TODO: NYI virtual QueryMaker* queryMaker(); virtual void startFullScanDevice(); // NOTE: incrementalscan and stopscan not implemented, might be used by UMS later though /** The protocol of uids coming from this collection. @return A string of the protocol, without the :// This has to be overridden for every device type, e.g. ipod:// */ virtual QString uidUrlProtocol() const { return QString(); } // TODO: NYI virtual QString collectionId() const; // uses udi virtual QString prettyName() const = 0; // NOTE: must be overridden based on device type virtual KIcon icon() const = 0; // NOTE: must be overridden based on device type virtual bool hasCapacity() const; virtual float usedCapacity() const; virtual float totalCapacity() const; // NOTE: location will have same method calls always, no need to redo each time virtual CollectionLocation* location() { return new MediaDeviceCollectionLocation( this ); } /** Capability-related methods */ virtual bool hasCapabilityInterface( Capabilities::Capability::Type type ) const; virtual Capabilities::Capability* createCapabilityInterface( Capabilities::Capability::Type type ); /** MediaDeviceCollection methods */ QString udi() const { return m_udi; } /** MediaDeviceCollection-specific */ Meta::MediaDeviceHandler* handler(); void init() { m_handler->init(); } // tell handler to start connection void emitCollectionReady(); virtual QAction *ejectAction() const; QSharedPointer memoryCollection() const { return m_mc; } void collectionUpdated() { emit updated(); } signals: void collectionReady( Collections::Collection* ); /** collectionDisconnected is called when ConnectionAssistant is told it is to be disconnected. This could be because another part of Amarok (e.g. applet) told it to or because the MediaDeviceMonitor noticed it disconnect */ void collectionDisconnected( const QString &udi ); void deletingCollection(); void attemptConnectionDone( bool success ); void copyTracksCompleted( bool success ); public slots: void slotAttemptConnectionDone( bool success ); virtual void eject(); void deleteCollection(); protected: MediaDeviceCollection(); QString m_udi; Meta::MediaDeviceHandler *m_handler; mutable QAction *m_ejectAction; QSharedPointer m_mc; }; } //namespace Collections #endif diff --git a/src/core-impl/collections/mediadevicecollection/MediaDeviceCollectionLocation.cpp b/src/core-impl/collections/mediadevicecollection/MediaDeviceCollectionLocation.cpp index 580234cb68..82a1621ad7 100644 --- a/src/core-impl/collections/mediadevicecollection/MediaDeviceCollectionLocation.cpp +++ b/src/core-impl/collections/mediadevicecollection/MediaDeviceCollectionLocation.cpp @@ -1,139 +1,139 @@ /**************************************************************************************** * Copyright (c) 2009 Alejandro Wainzinger * * * * 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, see . * ****************************************************************************************/ #include "MediaDeviceCollectionLocation.h" #include "MediaDeviceCache.h" // for collection refresh hack #include "core/meta/Meta.h" #include "core/support/Debug.h" #include "core-impl/collections/mediadevicecollection/MediaDeviceCollection.h" #include #include #include #include using namespace Collections; MediaDeviceCollectionLocation::MediaDeviceCollectionLocation( MediaDeviceCollection *collection ) : CollectionLocation( collection ) , m_collection( collection ) , m_handler( m_collection->handler() ) { //nothing to do } MediaDeviceCollectionLocation::~MediaDeviceCollectionLocation() { //nothing to do } QString MediaDeviceCollectionLocation::prettyLocation() const { return collection()->prettyName(); } bool MediaDeviceCollectionLocation::isWritable() const { return m_handler->isWritable(); } void MediaDeviceCollectionLocation::getKIOCopyableUrls( const Meta::TrackList &tracks ) { - connect( m_handler, SIGNAL(gotCopyableUrls(QMap)),SLOT(slotGetKIOCopyableUrlsDone(QMap)) ); + connect( m_handler, SIGNAL(gotCopyableUrls(QMap)),SLOT(slotGetKIOCopyableUrlsDone(QMap)) ); m_handler->getCopyableUrls( tracks ); } void -MediaDeviceCollectionLocation::copyUrlsToCollection( const QMap &sources, +MediaDeviceCollectionLocation::copyUrlsToCollection( const QMap &sources, const Transcoding::Configuration &configuration ) { DEBUG_BLOCK Q_UNUSED( configuration ) connect( m_handler, SIGNAL(copyTracksDone(bool)), this, SLOT(copyOperationFinished(bool)), Qt::QueuedConnection ); m_handler->copyTrackListToDevice( sources.keys() ); /* connect( m_collection, SIGNAL(copyTracksCompleted(bool)), SLOT(copyOperationFinished(bool)) ); // Copy list of tracks m_collection->copyTrackListToDevice( sources.keys() ); */ // TODO: call handler's method for copying a list of // tracks to the device. At the end, if successful, // write to database, and any unsuccessful track // copies will generate warning/error messages } void MediaDeviceCollectionLocation::copyOperationFinished( bool success ) { // TODO: should connect database written signal to // to this slot if( success ) { m_handler->writeDatabase(); } // TODO: will be replaced with a more powerful method // which deals with particular reasons for failed copies /* DEBUG_BLOCK if( !success ) { QMap failedTracks = m_collection->handler()->tracksFailed(); debug() << "The following tracks failed to copy"; foreach( Meta::TrackPtr track, failedTracks.keys() ) { // TODO: better error handling debug() << track->artist()->name() << " - " << track->name() << " with error: " << failedTracks[ track ]; source()->transferError( track, failedTracks[ track ] ); } } */ slotCopyOperationFinished(); } void MediaDeviceCollectionLocation::removeUrlsFromCollection( const Meta::TrackList &sources ) { DEBUG_BLOCK connect( m_handler, SIGNAL(removeTracksDone()), this, SLOT(removeOperationFinished()) ); m_handler->removeTrackListFromDevice( sources ); } void MediaDeviceCollectionLocation::removeOperationFinished() { DEBUG_BLOCK m_handler->writeDatabase(); slotRemoveOperationFinished(); } diff --git a/src/core-impl/collections/mediadevicecollection/MediaDeviceCollectionLocation.h b/src/core-impl/collections/mediadevicecollection/MediaDeviceCollectionLocation.h index e40529fbfc..07c056577a 100644 --- a/src/core-impl/collections/mediadevicecollection/MediaDeviceCollectionLocation.h +++ b/src/core-impl/collections/mediadevicecollection/MediaDeviceCollectionLocation.h @@ -1,65 +1,65 @@ /**************************************************************************************** * Copyright (c) 2009 Alejandro Wainzinger * * * * 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, see . * ****************************************************************************************/ #ifndef AMAROK_MEDIADEVICECOLLECTIONLOCATION_H #define AMAROK_MEDIADEVICECOLLECTIONLOCATION_H #include "core/collections/CollectionLocation.h" #include "core-impl/collections/mediadevicecollection/handler/MediaDeviceHandler.h" #include "core-impl/collections/mediadevicecollection/support/mediadevicecollection_export.h" #include #include #include class KJob; namespace Collections { class MediaDeviceCollection; class MEDIADEVICECOLLECTION_EXPORT MediaDeviceCollectionLocation : public CollectionLocation { Q_OBJECT public: MediaDeviceCollectionLocation( MediaDeviceCollection *collection ); virtual ~MediaDeviceCollectionLocation(); virtual QString prettyLocation() const; virtual bool isWritable() const; protected: virtual void getKIOCopyableUrls( const Meta::TrackList &tracks ); /// Copies these tracks to the Collection using the Handler - virtual void copyUrlsToCollection( const QMap &sources, + virtual void copyUrlsToCollection( const QMap &sources, const Transcoding::Configuration &configuration ); virtual void removeUrlsFromCollection( const Meta::TrackList &sources ); private slots: void copyOperationFinished( bool success ); void removeOperationFinished(); private: MediaDeviceCollection *m_collection; Meta::MediaDeviceHandler *m_handler; }; } //namespace Collections #endif diff --git a/src/core-impl/collections/mediadevicecollection/MediaDeviceMeta.cpp b/src/core-impl/collections/mediadevicecollection/MediaDeviceMeta.cpp index 4a419d5a01..fe48fbfa27 100644 --- a/src/core-impl/collections/mediadevicecollection/MediaDeviceMeta.cpp +++ b/src/core-impl/collections/mediadevicecollection/MediaDeviceMeta.cpp @@ -1,1024 +1,1024 @@ /**************************************************************************************** * Copyright (c) 2009 Alejandro Wainzinger * * * * 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, see . * ****************************************************************************************/ #include "MediaDeviceMeta.h" #include "SvgHandler.h" #include "core/capabilities/ActionsCapability.h" #include "core/support/Debug.h" #include "core-impl/capabilities/AlbumActionsCapability.h" #include "core-impl/collections/mediadevicecollection/MediaDeviceCollection.h" #include "core-impl/collections/mediadevicecollection/MediaDeviceTrackEditor.h" #include "core-impl/collections/mediadevicecollection/handler/capabilities/ArtworkCapability.h" #include "covermanager/CoverCache.h" #include "covermanager/CoverFetchingActions.h" #include -#include +#include #include using namespace Meta; MediaDeviceTrack::MediaDeviceTrack( Collections::MediaDeviceCollection *collection ) : Meta::Track() , m_collection( collection ) , m_artist( 0 ) , m_album( 0 ) , m_genre( 0 ) , m_composer( 0 ) , m_year( 0 ) , m_image() , m_comment() , m_name() , m_type() , m_bitrate( 0 ) , m_filesize( 0 ) , m_length( 0 ) , m_discNumber( 0 ) , m_samplerate( 0 ) , m_trackNumber( 0 ) , m_playCount( 0 ) , m_rating( 0 ) , m_bpm( 0 ) , m_playableUrl() { } MediaDeviceTrack::~MediaDeviceTrack() { //nothing to do } QString MediaDeviceTrack::name() const { return m_name; } -KUrl +QUrl MediaDeviceTrack::playableUrl() const { return m_playableUrl; } QString MediaDeviceTrack::uidUrl() const { return m_playableUrl.isLocalFile() ? m_playableUrl.toLocalFile() : m_playableUrl.url(); } QString MediaDeviceTrack::prettyUrl() const { if( m_playableUrl.isLocalFile() ) return m_playableUrl.toLocalFile(); QString collName = m_collection ? m_collection.data()->prettyName() : i18n( "Unknown Collection" ); QString artistName = artist()? artist()->prettyName() : i18n( "Unknown Artist" ); // Check name() to prevent infinite recursion QString trackName = !name().isEmpty()? prettyName() : i18n( "Unknown track" ); return QString( "%1: %2 - %3" ).arg( collName, artistName, trackName ); } QString MediaDeviceTrack::notPlayableReason() const { return localFileNotPlayableReason( playableUrl().toLocalFile() ); } bool MediaDeviceTrack::isEditable() const { if( m_collection ) return m_collection.data()->isWritable(); return false; } AlbumPtr MediaDeviceTrack::album() const { return AlbumPtr::staticCast( m_album ); } ArtistPtr MediaDeviceTrack::artist() const { return ArtistPtr::staticCast( m_artist ); } GenrePtr MediaDeviceTrack::genre() const { return GenrePtr::staticCast( m_genre ); } ComposerPtr MediaDeviceTrack::composer() const { return ComposerPtr::staticCast( m_composer ); } YearPtr MediaDeviceTrack::year() const { return YearPtr::staticCast( m_year ); } QString MediaDeviceTrack::comment() const { return m_comment; } void MediaDeviceTrack::setComment( const QString &newComment ) { m_comment = newComment; } double MediaDeviceTrack::score() const { return 0.0; } void MediaDeviceTrack::setScore( double newScore ) { Q_UNUSED( newScore ) } int MediaDeviceTrack::rating() const { return m_rating; } void MediaDeviceTrack::setRating( int newRating ) { if( newRating == m_rating ) return; m_rating = newRating; // this method is _not_ called though TrackEditor, notify observers manually notifyObservers(); } qint64 MediaDeviceTrack::length() const { return m_length; } void MediaDeviceTrack::setFileSize( int newFileSize ) { m_filesize = newFileSize; } int MediaDeviceTrack::filesize() const { return m_filesize; } int MediaDeviceTrack::bitrate() const { return m_bitrate; } void MediaDeviceTrack::setBitrate( int newBitrate ) { m_bitrate = newBitrate; } int MediaDeviceTrack::sampleRate() const { return m_samplerate; } void MediaDeviceTrack::setSamplerate( int newSamplerate ) { m_samplerate = newSamplerate; } qreal MediaDeviceTrack::bpm() const { return m_bpm; } void MediaDeviceTrack::setBpm( const qreal newBpm ) { m_bpm = newBpm; } int MediaDeviceTrack::trackNumber() const { return m_trackNumber; } void MediaDeviceTrack::setTrackNumber( int newTrackNumber ) { m_trackNumber = newTrackNumber; } int MediaDeviceTrack::discNumber() const { return m_discNumber; } void MediaDeviceTrack::setDiscNumber( int newDiscNumber ) { m_discNumber = newDiscNumber; } int MediaDeviceTrack::playCount() const { return m_playCount; } void MediaDeviceTrack::setPlayCount( const int newCount ) { m_playCount = newCount; } QDateTime MediaDeviceTrack::lastPlayed() const { return m_lastPlayed; } void MediaDeviceTrack::setLastPlayed( const QDateTime &newTime ) { m_lastPlayed = newTime; } qreal MediaDeviceTrack::replayGain( ReplayGainTag mode ) const { /* no known non-UMS portable media player is able to differentiante between different * replay gain modes (track & album), so store only one value */ switch( mode ) { case Meta::ReplayGain_Track_Gain: case Meta::ReplayGain_Album_Gain: return m_replayGain; case Meta::ReplayGain_Track_Peak: case Meta::ReplayGain_Album_Peak: // no default label so that compiler emits a warning when new enum value is added break; } return 0.0; } void MediaDeviceTrack::setReplayGain( qreal newReplayGain ) { m_replayGain = newReplayGain; } QString MediaDeviceTrack::type() const { if( m_type.isEmpty() && !m_playableUrl.path().isEmpty() ) { QString path = m_playableUrl.path(); return path.mid( path.lastIndexOf( '.' ) + 1 ); } return m_type; } void MediaDeviceTrack::setType( const QString & type ) { m_type = type; } void MediaDeviceTrack::prepareToPlay() { Meta::MediaDeviceTrackPtr ptr = Meta::MediaDeviceTrackPtr( this ); if( m_collection && m_collection.data()->handler() ) m_collection.data()->handler()->prepareToPlay( ptr ); } // TODO: employ observers (e.g. Handler) to take care of updated // data /* void MediaDeviceTrack::subscribe( Observer *observer ) { Q_UNUSED( observer ) //read only } void MediaDeviceTrack::unsubscribe( Observer *observer ) { Q_UNUSED( observer ) //read only } */ // TODO: implement this for MediaDeviceCollectionLocation bool MediaDeviceTrack::inCollection() const { return m_collection; // true is m_collection is not null pointer, false otherwise } Collections::Collection* MediaDeviceTrack::collection() const { return m_collection.data(); } TrackEditorPtr MediaDeviceTrack::editor() { return TrackEditorPtr( isEditable() ? new MediaDeviceTrackEditor( this ) : 0 ); } StatisticsPtr MediaDeviceTrack::statistics() { return StatisticsPtr( this ); } void MediaDeviceTrack::setAlbum( const QString &newAlbum ) { if( !m_collection ) return; MediaDeviceAlbumPtr albumPtr; MediaDeviceTrackPtr track( this ); AlbumMap albumMap = m_collection.data()->memoryCollection()->albumMap(); // do cleanup of soon to be previous album MediaDeviceArtistPtr albumArtist; QString albumArtistName; albumPtr = m_album; if ( !albumPtr.isNull() ) { albumArtist = MediaDeviceArtistPtr::staticCast( albumPtr->albumArtist() ); if( albumArtist ) albumArtistName = albumArtist->name(); // remove track from previous album's tracklist albumPtr->remTrack( track ); // if album's tracklist is empty, remove album from albummap if( albumPtr->tracks().isEmpty() ) albumMap.remove( AlbumPtr::staticCast( albumPtr ) ); } // change to a new album // check for the existence of the album to be set to, // if album exists, reuse, else create if ( albumMap.contains( newAlbum, albumArtistName ) ) { albumPtr = MediaDeviceAlbumPtr::staticCast( albumMap.value( newAlbum, albumArtistName ) ); } else { albumPtr = MediaDeviceAlbumPtr( new MediaDeviceAlbum( m_collection.data(), newAlbum ) ); albumPtr->setAlbumArtist( albumArtist ); albumMap.insert( AlbumPtr::staticCast( albumPtr ) ); } // add track to album's tracklist albumPtr->addTrack( track ); // set track's album to the new album setAlbum( albumPtr ); m_collection.data()->memoryCollection()->acquireWriteLock(); m_collection.data()->memoryCollection()->setAlbumMap( albumMap ); m_collection.data()->memoryCollection()->releaseLock(); } void MediaDeviceTrack::setAlbumArtist( const QString &newAlbumArtist ) { if( !m_collection ) return; if( m_album.isNull() || newAlbumArtist.isEmpty() ) return; MediaDeviceArtistPtr artistPtr; ArtistMap artistMap = m_collection.data()->memoryCollection()->artistMap(); if( artistMap.contains( newAlbumArtist ) ) artistPtr = MediaDeviceArtistPtr::staticCast( artistMap.value( newAlbumArtist ) ); else { artistPtr = MediaDeviceArtistPtr( new MediaDeviceArtist( newAlbumArtist ) ); artistMap.insert( newAlbumArtist, ArtistPtr::staticCast( artistPtr ) ); } m_album->setAlbumArtist( artistPtr ); m_collection.data()->memoryCollection()->acquireWriteLock(); m_collection.data()->memoryCollection()->setArtistMap( artistMap ); m_collection.data()->memoryCollection()->releaseLock(); } void MediaDeviceTrack::setArtist( const QString &newArtist ) { if( !m_collection ) return; MediaDeviceArtistPtr artistPtr; MediaDeviceTrackPtr track( this ); ArtistMap artistMap = m_collection.data()->memoryCollection()->artistMap(); // do cleanup of soon to be previous artist artistPtr = m_artist; // remove track from previous artist's tracklist if ( !artistPtr.isNull() ) { artistPtr->remTrack( track ); // if artist's tracklist is empty, remove artist from artistmap if( artistPtr->tracks().isEmpty() ) artistMap.remove( artistPtr->name() ); } // change to a new artist // check for the existence of the artist to be set to, // if artist exists, reuse, else create if ( artistMap.contains( newArtist ) ) { artistPtr = MediaDeviceArtistPtr::staticCast( artistMap.value( newArtist ) ); } else { artistPtr = MediaDeviceArtistPtr( new MediaDeviceArtist( newArtist ) ); artistMap.insert( newArtist, ArtistPtr::staticCast( artistPtr ) ); } // add track to artist's tracklist artistPtr->addTrack( track ); // set track's artist to the new artist setArtist( artistPtr ); m_collection.data()->memoryCollection()->acquireWriteLock(); m_collection.data()->memoryCollection()->setArtistMap( artistMap ); m_collection.data()->memoryCollection()->releaseLock(); } void MediaDeviceTrack::setGenre( const QString &newGenre ) { if( !m_collection ) return; MediaDeviceGenrePtr genrePtr; MediaDeviceTrackPtr track( this ); GenreMap genreMap = m_collection.data()->memoryCollection()->genreMap(); // do cleanup of soon to be previous genre genrePtr = m_genre; if ( !genrePtr.isNull() ) { // remove track from previous genre's tracklist genrePtr->remTrack( track ); // if genre's tracklist is empty, remove genre from genremap if( genrePtr->tracks().isEmpty() ) genreMap.remove( genrePtr->name() ); } // change to a new genre // check for the existence of the genre to be set to, // if genre exists, reuse, else create if ( genreMap.contains( newGenre ) ) { genrePtr = MediaDeviceGenrePtr::staticCast( genreMap.value( newGenre ) ); } else { genrePtr = MediaDeviceGenrePtr( new MediaDeviceGenre( newGenre ) ); genreMap.insert( newGenre, GenrePtr::staticCast( genrePtr ) ); } // add track to genre's tracklist genrePtr->addTrack( track ); // set track's genre to the new genre setGenre( genrePtr ); m_collection.data()->memoryCollection()->acquireWriteLock(); m_collection.data()->memoryCollection()->setGenreMap( genreMap ); m_collection.data()->memoryCollection()->releaseLock(); } void MediaDeviceTrack::setComposer( const QString &newComposer ) { if( !m_collection ) return; MediaDeviceComposerPtr composerPtr; MediaDeviceTrackPtr track( this ); ComposerMap composerMap = m_collection.data()->memoryCollection()->composerMap(); // do cleanup of soon to be previous composer composerPtr = m_composer; if ( !composerPtr.isNull() ) { // remove track from previous composer's tracklist composerPtr->remTrack( track ); // if composer's tracklist is empty, remove composer from composermap if( composerPtr->tracks().isEmpty() ) composerMap.remove( composerPtr->name() ); } // change to a new composer // check for the existence of the composer to be set to, // if composer exists, reuse, else create if ( composerMap.contains( newComposer ) ) { composerPtr = MediaDeviceComposerPtr::staticCast( composerMap.value( newComposer ) ); } else { composerPtr = MediaDeviceComposerPtr( new MediaDeviceComposer( newComposer ) ); composerMap.insert( newComposer, ComposerPtr::staticCast( composerPtr ) ); } // add track to composer's tracklist composerPtr->addTrack( track ); // set track's composer to the new composer setComposer( composerPtr ); m_collection.data()->memoryCollection()->acquireWriteLock(); m_collection.data()->memoryCollection()->setComposerMap( composerMap ); m_collection.data()->memoryCollection()->releaseLock(); } void MediaDeviceTrack::setYear( int newYear ) { if( !m_collection ) return; MediaDeviceYearPtr yearPtr; MediaDeviceTrackPtr track( this ); YearMap yearMap = m_collection.data()->memoryCollection()->yearMap(); // do cleanup of soon to be previous year yearPtr = m_year; if ( !yearPtr.isNull() ) { // remove track from previous year's tracklist yearPtr->remTrack( track ); // if year's tracklist is empty, remove year from yearmap if( yearPtr->tracks().isEmpty() ) yearMap.remove( yearPtr->year() ); } // change to a new year // check for the existence of the year to be set to, // if year exists, reuse, else create if ( yearMap.contains( newYear ) ) { yearPtr = MediaDeviceYearPtr::staticCast( yearMap.value( newYear ) ); } else { yearPtr = MediaDeviceYearPtr( new MediaDeviceYear( QString::number(newYear) ) ); yearMap.insert( newYear, YearPtr::staticCast( yearPtr ) ); } // add track to year's tracklist yearPtr->addTrack( track ); // set track's year to the new year setYear( yearPtr ); m_collection.data()->memoryCollection()->acquireWriteLock(); m_collection.data()->memoryCollection()->setYearMap( yearMap ); m_collection.data()->memoryCollection()->releaseLock(); } void MediaDeviceTrack::setAlbum( MediaDeviceAlbumPtr album ) { m_album = album; } void MediaDeviceTrack::setArtist( MediaDeviceArtistPtr artist ) { m_artist = artist; } void MediaDeviceTrack::setGenre( MediaDeviceGenrePtr genre ) { m_genre = genre; } void MediaDeviceTrack::setComposer( MediaDeviceComposerPtr composer ) { m_composer = composer; } void MediaDeviceTrack::setYear( MediaDeviceYearPtr year ) { m_year = year; } QString MediaDeviceTrack::title() const { return m_name; } void MediaDeviceTrack::setTitle( const QString &title ) { m_name = title; } void MediaDeviceTrack::setLength( qint64 length ) { m_length = length; } void MediaDeviceTrack::commitChanges() { notifyObservers(); } //MediaDeviceArtist MediaDeviceArtist::MediaDeviceArtist( const QString &name ) : Meta::Artist() , m_name( name ) , m_tracks() { //nothing to do } MediaDeviceArtist::~MediaDeviceArtist() { //nothing to do } QString MediaDeviceArtist::name() const { return m_name; } TrackList MediaDeviceArtist::tracks() { return m_tracks; } void MediaDeviceArtist::addTrack( MediaDeviceTrackPtr track ) { m_tracks.append( TrackPtr::staticCast( track ) ); } void MediaDeviceArtist::remTrack( MediaDeviceTrackPtr track ) { m_tracks.removeOne( TrackPtr::staticCast( track ) ); } //---------------MediaDeviceAlbum----------------------------------- MediaDeviceAlbum::MediaDeviceAlbum( Collections::MediaDeviceCollection *collection, const QString &name ) : Meta::Album() , m_collection( collection ) , m_artworkCapability() , m_name( name ) , m_tracks() , m_isCompilation( false ) , m_hasImagePossibility( true ) // assume it has a cover until proven otherwise , m_hasImageChecked( false ) , m_image( QImage() ) , m_albumArtist( 0 ) { MediaDeviceHandler *handler = m_collection.data()->handler(); if( handler && handler->hasCapabilityInterface( Handler::Capability::Artwork ) ) m_artworkCapability = handler->create(); } MediaDeviceAlbum::~MediaDeviceAlbum() { if( m_artworkCapability ) m_artworkCapability.data()->deleteLater(); CoverCache::invalidateAlbum( this ); } QString MediaDeviceAlbum::name() const { return m_name; } bool MediaDeviceAlbum::isCompilation() const { return m_isCompilation; } void MediaDeviceAlbum::setIsCompilation( bool compilation ) { m_isCompilation = compilation; } bool MediaDeviceAlbum::hasAlbumArtist() const { return !m_albumArtist.isNull(); } ArtistPtr MediaDeviceAlbum::albumArtist() const { return ArtistPtr::staticCast( m_albumArtist ); } TrackList MediaDeviceAlbum::tracks() { return m_tracks; } bool MediaDeviceAlbum::hasImage( int size ) const { Q_UNUSED( size ) if( !m_hasImageChecked ) m_hasImagePossibility = ! const_cast( this )->image().isNull(); return m_hasImagePossibility; } QImage MediaDeviceAlbum::image( int size ) const { if( m_name.isEmpty() || !m_hasImagePossibility || m_tracks.isEmpty() ) return Meta::Album::image( size ); if( m_image.isNull() && m_artworkCapability ) { MediaDeviceTrackPtr track = MediaDeviceTrackPtr::staticCast( m_tracks.first() ); m_image = m_artworkCapability.data()->getCover( track ); m_hasImagePossibility = !m_image.isNull(); m_hasImageChecked = true; CoverCache::invalidateAlbum( this ); } if( !m_image.isNull() ) { if( !size ) return m_image; return m_image.scaled( QSize( size, size ), Qt::KeepAspectRatio ); } return Meta::Album::image( size ); } bool MediaDeviceAlbum::canUpdateImage() const { if( m_artworkCapability ) return m_artworkCapability.data()->canUpdateCover(); return false; } void MediaDeviceAlbum::setImage( const QImage &image ) { if( m_artworkCapability && m_artworkCapability.data()->canUpdateCover() ) { // reset to initial values, let next call to image() re-fetch it m_hasImagePossibility = true; m_hasImageChecked = false; m_artworkCapability.data()->setCover( MediaDeviceAlbumPtr( this ), image ); CoverCache::invalidateAlbum( this ); } } void MediaDeviceAlbum::setImagePath( const QString &path ) { if( m_artworkCapability && m_artworkCapability.data()->canUpdateCover() ) { // reset to initial values, let next call to image() re-fetch it m_hasImagePossibility = true; m_hasImageChecked = false; m_artworkCapability.data()->setCoverPath( MediaDeviceAlbumPtr( this ), path ); CoverCache::invalidateAlbum( this ); } } bool MediaDeviceAlbum::hasCapabilityInterface( Capabilities::Capability::Type type ) const { switch( type ) { case Capabilities::Capability::Actions: return true; default: return false; } } Capabilities::Capability* MediaDeviceAlbum::createCapabilityInterface( Capabilities::Capability::Type type ) { switch( type ) { case Capabilities::Capability::Actions: return new Capabilities::AlbumActionsCapability( Meta::AlbumPtr( this ) ); default: return 0; } } void MediaDeviceAlbum::addTrack( MediaDeviceTrackPtr track ) { m_tracks.append( TrackPtr::staticCast( track ) ); } void MediaDeviceAlbum::remTrack( MediaDeviceTrackPtr track ) { m_tracks.removeOne( TrackPtr::staticCast( track ) ); } void MediaDeviceAlbum::setAlbumArtist( MediaDeviceArtistPtr artist ) { m_albumArtist = artist; } //MediaDeviceComposer MediaDeviceComposer::MediaDeviceComposer( const QString &name ) : Meta::Composer() , m_name( name ) , m_tracks() { //nothing to do } MediaDeviceComposer::~MediaDeviceComposer() { //nothing to do } QString MediaDeviceComposer::name() const { return m_name; } TrackList MediaDeviceComposer::tracks() { return m_tracks; } void MediaDeviceComposer::addTrack( MediaDeviceTrackPtr track ) { m_tracks.append( TrackPtr::staticCast( track ) ); } void MediaDeviceComposer::remTrack( MediaDeviceTrackPtr track ) { m_tracks.removeOne( TrackPtr::staticCast( track ) ); } //---------------MediaDeviceGenre----------------------------------- MediaDeviceGenre::MediaDeviceGenre( const QString &name ) : Meta::Genre() , m_name( name ) , m_tracks() { //nothing to do } MediaDeviceGenre::~MediaDeviceGenre() { //nothing to do } QString MediaDeviceGenre::name() const { return m_name; } TrackList MediaDeviceGenre::tracks() { return m_tracks; } void MediaDeviceGenre::addTrack( MediaDeviceTrackPtr track ) { m_tracks.append( TrackPtr::staticCast( track ) ); } void MediaDeviceGenre::remTrack( MediaDeviceTrackPtr track ) { m_tracks.removeOne( TrackPtr::staticCast( track ) ); } //MediaDeviceYear MediaDeviceYear::MediaDeviceYear( const QString &name ) : Meta::Year() , m_name( name ) , m_tracks() { //nothing to do } MediaDeviceYear::~MediaDeviceYear() { //nothing to do } QString MediaDeviceYear::name() const { return m_name; } TrackList MediaDeviceYear::tracks() { return m_tracks; } void MediaDeviceYear::addTrack( MediaDeviceTrackPtr track ) { m_tracks.append( TrackPtr::staticCast( track ) ); } void MediaDeviceYear::remTrack( MediaDeviceTrackPtr track ) { m_tracks.removeOne( TrackPtr::staticCast( track ) ); } diff --git a/src/core-impl/collections/mediadevicecollection/MediaDeviceMeta.h b/src/core-impl/collections/mediadevicecollection/MediaDeviceMeta.h index 5695a480ef..e1e94cd238 100644 --- a/src/core-impl/collections/mediadevicecollection/MediaDeviceMeta.h +++ b/src/core-impl/collections/mediadevicecollection/MediaDeviceMeta.h @@ -1,312 +1,312 @@ /**************************************************************************************** * Copyright (c) 2009 Alejandro Wainzinger * * * * 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, see . * ****************************************************************************************/ #ifndef MEDIADEVICEMETA_H #define MEDIADEVICEMETA_H #include "core/meta/Meta.h" #include "core/meta/Statistics.h" #include "core/support/Debug.h" #include "core-impl/collections/mediadevicecollection/support/mediadevicecollection_export.h" #include #include #include namespace Collections { class MediaDeviceCollection; } class QAction; namespace Handler { class ArtworkCapability; } namespace Meta { class MediaDeviceTrack; class MediaDeviceAlbum; class MediaDeviceArtist; class MediaDeviceGenre; class MediaDeviceComposer; class MediaDeviceYear; typedef KSharedPtr MediaDeviceTrackPtr; typedef KSharedPtr MediaDeviceArtistPtr; typedef KSharedPtr MediaDeviceAlbumPtr; typedef KSharedPtr MediaDeviceGenrePtr; typedef KSharedPtr MediaDeviceComposerPtr; typedef KSharedPtr MediaDeviceYearPtr; typedef QList MediaDeviceTrackList; class MEDIADEVICECOLLECTION_EXPORT MediaDeviceTrack : public Meta::Track, public Statistics { public: MediaDeviceTrack( Collections::MediaDeviceCollection *collection ); virtual ~MediaDeviceTrack(); virtual QString name() const; - virtual KUrl playableUrl() const; + virtual QUrl playableUrl() const; virtual QString uidUrl() const; virtual QString prettyUrl() const; virtual QString notPlayableReason() const; bool isEditable() const; virtual AlbumPtr album() const; virtual ArtistPtr artist() const; virtual GenrePtr genre() const; virtual ComposerPtr composer() const; virtual YearPtr year() const; virtual void setAlbum ( const QString &newAlbum ); virtual void setAlbumArtist( const QString &newAlbumArtist ); virtual void setArtist ( const QString &newArtist ); virtual void setGenre ( const QString &newGenre ); virtual void setComposer ( const QString &newComposer ); virtual void setYear ( int newYear ); virtual QString title() const; virtual void setTitle( const QString &newTitle ); virtual QString comment() const; virtual void setComment ( const QString &newComment ); virtual qint64 length() const; void setFileSize( int newFileSize ); virtual int filesize() const; virtual int bitrate() const; virtual void setBitrate( int newBitrate ); virtual int sampleRate() const; virtual void setSamplerate( int newSamplerate ); virtual qreal bpm() const; virtual void setBpm( const qreal newBpm ); virtual int trackNumber() const; virtual void setTrackNumber ( int newTrackNumber ); virtual int discNumber() const; virtual void setDiscNumber ( int newDiscNumber ); virtual qreal replayGain( ReplayGainTag mode ) const; /* Set the track replay gain (other types unsupported) */ void setReplayGain( qreal newReplayGain ); virtual QString type() const; virtual void prepareToPlay(); virtual bool inCollection() const; virtual Collections::Collection* collection() const; virtual TrackEditorPtr editor(); virtual StatisticsPtr statistics(); // Meta::Statistics methods virtual double score() const; virtual void setScore ( double newScore ); virtual int rating() const; virtual void setRating ( int newRating ); virtual QDateTime lastPlayed() const; void setLastPlayed( const QDateTime &newTime ); // firstPlayed() not available in any media device virtual int playCount() const; void setPlayCount( const int newCount ); //MediaDeviceTrack specific methods // These methods are for Handler void setAlbum( MediaDeviceAlbumPtr album ); void setArtist( MediaDeviceArtistPtr artist ); void setComposer( MediaDeviceComposerPtr composer ); void setGenre( MediaDeviceGenrePtr genre ); void setYear( MediaDeviceYearPtr year ); void setType( const QString & type ); void setLength( qint64 length ); - void setPlayableUrl( const KUrl &url) { m_playableUrl = url; } + void setPlayableUrl( const QUrl &url) { m_playableUrl = url; } /** * Notifies observers about changes to metadata, one of the observers is media * device handler which writes the changes back to the device. */ void commitChanges(); private: QWeakPointer m_collection; MediaDeviceArtistPtr m_artist; MediaDeviceAlbumPtr m_album; MediaDeviceGenrePtr m_genre; MediaDeviceComposerPtr m_composer; MediaDeviceYearPtr m_year; // For MediaDeviceTrack-specific use QImage m_image; QString m_comment; QString m_name; QString m_type; int m_bitrate; int m_filesize; qint64 m_length; int m_discNumber; int m_samplerate; int m_trackNumber; int m_playCount; QDateTime m_lastPlayed; int m_rating; qreal m_bpm; qreal m_replayGain; QString m_displayUrl; - KUrl m_playableUrl; + QUrl m_playableUrl; }; class MEDIADEVICECOLLECTION_EXPORT MediaDeviceArtist : public Meta::Artist { public: MediaDeviceArtist( const QString &name ); virtual ~MediaDeviceArtist(); virtual QString name() const; virtual TrackList tracks(); //MediaDeviceArtist specific methods virtual void addTrack( MediaDeviceTrackPtr track ); virtual void remTrack( MediaDeviceTrackPtr track ); private: QString m_name; TrackList m_tracks; }; class MEDIADEVICECOLLECTION_EXPORT MediaDeviceAlbum : public Meta::Album { public: MediaDeviceAlbum( Collections::MediaDeviceCollection *collection, const QString &name ); virtual ~MediaDeviceAlbum(); virtual QString name() const; virtual bool isCompilation() const; void setIsCompilation( bool compilation ); virtual bool hasAlbumArtist() const; virtual ArtistPtr albumArtist() const; virtual TrackList tracks(); virtual bool hasImage( int size = 0 ) const; virtual QImage image( int size = 0 ) const; virtual bool canUpdateImage() const; virtual void setImage( const QImage &image ); virtual void setImagePath( const QString &path ); virtual bool hasCapabilityInterface( Capabilities::Capability::Type type ) const; virtual Capabilities::Capability* createCapabilityInterface( Capabilities::Capability::Type type ); //MediaDeviceAlbum specific methods void addTrack( MediaDeviceTrackPtr track ); void remTrack( MediaDeviceTrackPtr track ); void setAlbumArtist( MediaDeviceArtistPtr artist ); private: QWeakPointer m_collection; QWeakPointer m_artworkCapability; QString m_name; TrackList m_tracks; bool m_isCompilation; mutable bool m_hasImagePossibility; mutable bool m_hasImageChecked; mutable QImage m_image; MediaDeviceArtistPtr m_albumArtist; }; class MEDIADEVICECOLLECTION_EXPORT MediaDeviceComposer : public Meta::Composer { public: MediaDeviceComposer( const QString &name ); virtual ~MediaDeviceComposer(); virtual QString name() const; virtual TrackList tracks(); //MediaDeviceComposer specific methods void addTrack( MediaDeviceTrackPtr track ); void remTrack( MediaDeviceTrackPtr track ); private: QString m_name; TrackList m_tracks; }; class MEDIADEVICECOLLECTION_EXPORT MediaDeviceGenre : public Meta::Genre { public: MediaDeviceGenre( const QString &name ); virtual ~MediaDeviceGenre(); virtual QString name() const; virtual TrackList tracks(); //MediaDeviceGenre specific methods void addTrack( MediaDeviceTrackPtr track ); void remTrack( MediaDeviceTrackPtr track ); private: QString m_name; TrackList m_tracks; }; class MEDIADEVICECOLLECTION_EXPORT MediaDeviceYear : public Meta::Year { public: MediaDeviceYear( const QString &name ); virtual ~MediaDeviceYear(); virtual QString name() const; virtual TrackList tracks(); //MediaDeviceYear specific methods void addTrack( MediaDeviceTrackPtr track ); void remTrack( MediaDeviceTrackPtr track ); private: QString m_name; TrackList m_tracks; }; } #endif diff --git a/src/core-impl/collections/mediadevicecollection/handler/MediaDeviceHandler.cpp b/src/core-impl/collections/mediadevicecollection/handler/MediaDeviceHandler.cpp index 67784ae571..3bdd5d1715 100644 --- a/src/core-impl/collections/mediadevicecollection/handler/MediaDeviceHandler.cpp +++ b/src/core-impl/collections/mediadevicecollection/handler/MediaDeviceHandler.cpp @@ -1,1245 +1,1245 @@ /**************************************************************************************** * Copyright (c) 2009 Alejandro Wainzinger * * * * 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) version 3 or * * any later version accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * 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, see . * ****************************************************************************************/ #include "MediaDeviceHandler.h" #include "core/interfaces/Logger.h" #include "core/support/Components.h" #include "core-impl/collections/mediadevicecollection/MediaDeviceCollection.h" #include "core-impl/collections/mediadevicecollection/handler/MediaDeviceHandlerCapability.h" #include "core-impl/collections/support/ArtistHelper.h" #include "playlistmanager/PlaylistManager.h" #include #include #include using namespace Meta; bool MetaHandlerCapability::hasCapabilityInterface( Handler::Capability::Type type ) const { Q_UNUSED( type ); return false; } Handler::Capability* MetaHandlerCapability::createCapabilityInterface( Handler::Capability::Type type ) { Q_UNUSED( type ); return 0; } MediaDeviceHandler::MediaDeviceHandler( QObject *parent ) : QObject( parent ) , m_memColl( qobject_cast(parent) ) , m_provider( 0 ) , m_isCopying( false ) , m_isDeleting( false ) , m_pc( 0 ) , m_rc( 0 ) , m_wc( 0 ) { DEBUG_BLOCK connect( m_memColl, SIGNAL(deletingCollection()), this, SLOT(slotDeletingHandler()), Qt::QueuedConnection ); connect( this, SIGNAL(databaseWritten(bool)), this, SLOT(slotDatabaseWritten(bool)), Qt::QueuedConnection ); } MediaDeviceHandler::~MediaDeviceHandler() { DEBUG_BLOCK delete m_provider; } void MediaDeviceHandler::slotDeletingHandler() { DEBUG_BLOCK if( m_provider ) The::playlistManager()->removeProvider( m_provider ); m_memColl = NULL; } void MediaDeviceHandler::getBasicMediaDeviceTrackInfo( const Meta::MediaDeviceTrackPtr &srcTrack, Meta::MediaDeviceTrackPtr destTrack ) { /* 1-liner info retrieval */ destTrack->setTitle( m_rc->libGetTitle( srcTrack ) ); destTrack->setLength( m_rc->libGetLength( srcTrack ) ); destTrack->setTrackNumber( m_rc->libGetTrackNumber( srcTrack ) ); destTrack->setComment( m_rc->libGetComment( srcTrack ) ); destTrack->setDiscNumber( m_rc->libGetDiscNumber( srcTrack ) ); destTrack->setBitrate( m_rc->libGetBitrate( srcTrack ) ); destTrack->setSamplerate( m_rc->libGetSamplerate( srcTrack ) ); destTrack->setBpm( m_rc->libGetBpm( srcTrack ) ); destTrack->setFileSize( m_rc->libGetFileSize( srcTrack ) ); destTrack->setPlayCount( m_rc->libGetPlayCount( srcTrack ) ); destTrack->setLastPlayed( m_rc->libGetLastPlayed( srcTrack ) ); destTrack->setRating( m_rc->libGetRating( srcTrack ) ); destTrack->setReplayGain( m_rc->libGetReplayGain( srcTrack ) ); destTrack->setPlayableUrl( m_rc->libGetPlayableUrl( srcTrack ) ); destTrack->setType( m_rc->libGetType( srcTrack ) ); } void MediaDeviceHandler::setBasicMediaDeviceTrackInfo( const Meta::TrackPtr& srcTrack, MediaDeviceTrackPtr destTrack ) { DEBUG_BLOCK if( !setupWriteCapability() ) return; m_wc->libSetTitle( destTrack, srcTrack->name() ); QString albumArtist; bool isCompilation = false; if ( srcTrack->album() ) { AlbumPtr album = srcTrack->album(); m_wc->libSetAlbum( destTrack, album->name() ); isCompilation = album->isCompilation(); m_wc->libSetIsCompilation( destTrack, isCompilation ); if( album->hasAlbumArtist() ) albumArtist = album->albumArtist()->name(); if( album->hasImage() ) m_wc->libSetCoverArt( destTrack, album->image() ); } QString trackArtist; if ( srcTrack->artist() ) { trackArtist = srcTrack->artist()->name(); m_wc->libSetArtist( destTrack, trackArtist ); } QString composer; if ( srcTrack->composer() ) { composer = srcTrack->composer()->name(); m_wc->libSetComposer( destTrack, composer ); } QString genre; if ( srcTrack->genre() ) { genre = srcTrack->genre()->name(); m_wc->libSetGenre( destTrack, genre ); } if( isCompilation && albumArtist.isEmpty() ) // iPod doesn't handle empy album artist well for compilation albums (splits these albums) albumArtist = i18n( "Various Artists" ); else albumArtist = ArtistHelper::bestGuessAlbumArtist( albumArtist, trackArtist, genre, composer ); m_wc->libSetAlbumArtist( destTrack, albumArtist ); if ( srcTrack->year() ) m_wc->libSetYear( destTrack, srcTrack->year()->name() ); m_wc->libSetLength( destTrack, srcTrack->length() ); m_wc->libSetTrackNumber( destTrack, srcTrack->trackNumber() ); m_wc->libSetComment( destTrack, srcTrack->comment() ); m_wc->libSetDiscNumber( destTrack, srcTrack->discNumber() ); m_wc->libSetBitrate( destTrack, srcTrack->bitrate() ); m_wc->libSetSamplerate( destTrack, srcTrack->sampleRate() ); m_wc->libSetBpm( destTrack, srcTrack->bpm() ); m_wc->libSetFileSize( destTrack, srcTrack->filesize() ); m_wc->libSetPlayCount( destTrack, srcTrack->statistics()->playCount() ); m_wc->libSetLastPlayed( destTrack, srcTrack->statistics()->lastPlayed() ); m_wc->libSetRating( destTrack, srcTrack->statistics()->rating() ); // MediaDeviceTrack stores only track gain: m_wc->libSetReplayGain( destTrack, srcTrack->replayGain( Meta::ReplayGain_Track_Gain ) ); m_wc->libSetType( destTrack, srcTrack->type() ); //libSetPlayableUrl( destTrack, srcTrack ); } void MediaDeviceHandler::addMediaDeviceTrackToCollection( Meta::MediaDeviceTrackPtr& track ) { if( !setupReadCapability() ) return; TrackMap trackMap = m_memColl->memoryCollection()->trackMap(); ArtistMap artistMap = m_memColl->memoryCollection()->artistMap(); AlbumMap albumMap = m_memColl->memoryCollection()->albumMap(); GenreMap genreMap = m_memColl->memoryCollection()->genreMap(); ComposerMap composerMap = m_memColl->memoryCollection()->composerMap(); YearMap yearMap = m_memColl->memoryCollection()->yearMap(); /* 1-liner info retrieval */ //Meta::TrackPtr srcTrack = Meta::TrackPtr::staticCast( track ); //getBasicMediaDeviceTrackInfo( srcTrack, track ); /* map-related info retrieval */ // NB: setupArtistMap _MUST_ be called before setupAlbumMap setupArtistMap( track, artistMap ); setupAlbumMap( track, albumMap, artistMap ); setupGenreMap( track, genreMap ); setupComposerMap( track, composerMap ); setupYearMap( track, yearMap ); /* trackmap also soon to be subordinated */ trackMap.insert( track->uidUrl(), TrackPtr::staticCast( track ) ); m_titlemap.insert( track->name(), TrackPtr::staticCast( track ) ); // Finally, assign the created maps to the collection m_memColl->memoryCollection()->acquireWriteLock(); m_memColl->memoryCollection()->setTrackMap( trackMap ); m_memColl->memoryCollection()->setArtistMap( artistMap ); m_memColl->memoryCollection()->setAlbumMap( albumMap ); m_memColl->memoryCollection()->setGenreMap( genreMap ); m_memColl->memoryCollection()->setComposerMap( composerMap ); m_memColl->memoryCollection()->setYearMap( yearMap ); m_memColl->memoryCollection()->releaseLock(); } void MediaDeviceHandler::removeMediaDeviceTrackFromCollection( Meta::MediaDeviceTrackPtr &track ) { TrackMap trackMap = m_memColl->memoryCollection()->trackMap(); ArtistMap artistMap = m_memColl->memoryCollection()->artistMap(); AlbumMap albumMap = m_memColl->memoryCollection()->albumMap(); GenreMap genreMap = m_memColl->memoryCollection()->genreMap(); ComposerMap composerMap = m_memColl->memoryCollection()->composerMap(); YearMap yearMap = m_memColl->memoryCollection()->yearMap(); Meta::MediaDeviceArtistPtr artist = Meta::MediaDeviceArtistPtr::dynamicCast( track->artist() ); Meta::MediaDeviceAlbumPtr album = Meta::MediaDeviceAlbumPtr::dynamicCast( track->album() ); Meta::MediaDeviceGenrePtr genre = Meta::MediaDeviceGenrePtr::dynamicCast( track->genre() ); Meta::MediaDeviceComposerPtr composer = Meta::MediaDeviceComposerPtr::dynamicCast( track->composer() ); Meta::MediaDeviceYearPtr year = Meta::MediaDeviceYearPtr::dynamicCast( track->year() ); // remove track from metadata's tracklists artist->remTrack( track ); album->remTrack( track ); genre->remTrack( track ); composer->remTrack( track ); year->remTrack( track ); // if empty, get rid of metadata in general if( artist->tracks().isEmpty() ) { artistMap.remove( artist->name() ); m_memColl->memoryCollection()->acquireWriteLock(); m_memColl->memoryCollection()->setArtistMap( artistMap ); m_memColl->memoryCollection()->releaseLock(); } if( album->tracks().isEmpty() ) { albumMap.remove( AlbumPtr::staticCast( album ) ); m_memColl->memoryCollection()->acquireWriteLock(); m_memColl->memoryCollection()->setAlbumMap( albumMap ); m_memColl->memoryCollection()->releaseLock(); } if( genre->tracks().isEmpty() ) { genreMap.remove( genre->name() ); m_memColl->memoryCollection()->acquireWriteLock(); m_memColl->memoryCollection()->setGenreMap( genreMap ); m_memColl->memoryCollection()->releaseLock(); } if( composer->tracks().isEmpty() ) { composerMap.remove( composer->name() ); m_memColl->memoryCollection()->acquireWriteLock(); m_memColl->memoryCollection()->setComposerMap( composerMap ); m_memColl->memoryCollection()->releaseLock(); } if( year->tracks().isEmpty() ) { yearMap.remove( year->year() ); m_memColl->memoryCollection()->acquireWriteLock(); m_memColl->memoryCollection()->setYearMap( yearMap ); m_memColl->memoryCollection()->releaseLock(); } // remove from trackmap trackMap.remove( track->uidUrl() ); m_titlemap.remove( track->name(), TrackPtr::staticCast( track ) ); // Finally, assign the created maps to the collection m_memColl->memoryCollection()->acquireWriteLock(); m_memColl->memoryCollection()->setTrackMap( trackMap ); m_memColl->memoryCollection()->setArtistMap( artistMap ); m_memColl->memoryCollection()->setAlbumMap( albumMap ); m_memColl->memoryCollection()->setGenreMap( genreMap ); m_memColl->memoryCollection()->setComposerMap( composerMap ); m_memColl->memoryCollection()->setYearMap( yearMap ); m_memColl->memoryCollection()->releaseLock(); } void MediaDeviceHandler::getCopyableUrls(const Meta::TrackList &tracks) { - QMap urls; + QMap urls; foreach( Meta::TrackPtr track, tracks ) { if( track->isPlayable() ) urls.insert( track, track->playableUrl() ); } emit gotCopyableUrls( urls ); } void MediaDeviceHandler::copyTrackListToDevice(const Meta::TrackList tracklist) { DEBUG_BLOCK const QString copyErrorCaption = i18n( "Copying Tracks Failed" ); if ( m_isCopying ) { KMessageBox::error( 0, i18n( "Tracks not copied: the device is already being copied to" ), copyErrorCaption ); return; } setupReadCapability(); if( !setupWriteCapability() ) return; m_isCopying = true; bool isDupe = false; bool hasError = false; QString format; TrackMap trackMap = m_memColl->memoryCollection()->trackMap(); Meta::TrackList tempTrackList; m_copyFailed = false; m_tracksFailed.clear(); // Clear Transfer queue m_tracksToCopy.clear(); // Check for same tags, don't copy if same tags // Also check for compatible format foreach( Meta::TrackPtr track, tracklist ) { // Check for compatible formats format = track->type(); if( !m_wc->supportedFormats().contains( format ) ) { const QString error = i18n("Unsupported format: %1", format); m_tracksFailed.insert( track, error ); hasError = true; continue; } tempTrackList = m_titlemap.values( track->name() ); /* If no song with same title, already not a dupe */ if( tempTrackList.isEmpty() ) { debug() << "No tracks with same title, track not a dupe"; m_tracksToCopy.append( track ); continue; } /* Songs with same title present, check other tags */ isDupe = false; foreach( Meta::TrackPtr tempTrack, tempTrackList ) { if( ( tempTrack->artist()->name() != track->artist()->name() ) || ( tempTrack->album()->name() != track->album()->name() ) || ( tempTrack->genre()->name() != track->genre()->name() ) || ( tempTrack->composer()->name() != track->composer()->name() ) || ( tempTrack->year()->name() != track->year()->name() ) ) { continue; } // Track is already on there, break isDupe = true; hasError = true; break; } if( !isDupe ) m_tracksToCopy.append( track ); else { debug() << "Track " << track->name() << " is a dupe!"; const QString error = i18n("Already on device"); m_tracksFailed.insert( track, error ); } } // NOTE: see comment at top of copyTrackListToDevice if( hasError ) m_copyFailed = true; /* List ready, begin copying */ // Do not bother copying 0 tracks // This could happen if all tracks to copy are dupes if( m_tracksToCopy.size() == 0 ) { KMessageBox::error( 0, i18n( "Tracks not copied: the device already has these tracks" ), copyErrorCaption ); m_isCopying = false; emit copyTracksDone( false ); return; } // Check for available space, in bytes, after the copy int transfersize = 0; foreach( Meta::TrackPtr track, m_tracksToCopy ) transfersize += track->filesize(); // NOTE: if the device will not have more than 5 MB to spare, abort the copy // This is important because on some devices if there is insufficient space to write the database, it will appear as // though all music has been wiped off the device - since neither the device nor amarok will be able to read the // (corrupted) database. if( !( (freeSpace() - transfersize) > 1024*1024*5 ) ) { debug() << "Free space: " << freeSpace(); debug() << "Space would've been after copy: " << (freeSpace() - transfersize); KMessageBox::error( 0, i18n( "Tracks not copied: the device has insufficient space" ), copyErrorCaption ); m_isCopying = false; emit copyTracksDone( false ); return; } debug() << "Copying " << m_tracksToCopy.size() << " tracks"; // Set up progress bar Amarok::Components::logger()->newProgressOperation( this, i18n( "Transferring Tracks to Device" ), m_tracksToCopy.size() ); // prepare to copy m_wc->prepareToCopy(); m_numTracksToCopy = m_tracksToCopy.count(); m_tracksCopying.clear(); m_trackSrcDst.clear(); // begin copying tracks to device if( !m_copyingthreadsafe ) copyNextTrackToDevice(); else enqueueNextCopyThread(); } void MediaDeviceHandler::copyNextTrackToDevice() { DEBUG_BLOCK Meta::TrackPtr track; debug() << "Tracks left to copy after this one is now done: " << m_numTracksToCopy; if ( !m_tracksToCopy.isEmpty() ) { // Pop the track off the front of the list track = m_tracksToCopy.takeFirst(); // Copy the track and check result if ( !privateCopyTrackToDevice( track ) ) slotCopyTrackFailed( track ); } else { if ( m_numTracksToCopy > 0 ) debug() << "Oops. \"Tracks to copy\" counter is not zero, but copy list is empty. Something missed?"; if ( m_copyFailed ) { Amarok::Components::logger()->shortMessage( i18np( "%1 track failed to copy to the device", "%1 tracks failed to copy to the device", m_tracksFailed.size() ) ); } // clear maps/hashes used m_tracksCopying.clear(); m_trackSrcDst.clear(); m_tracksFailed.clear(); m_tracksToCopy.clear(); // copying done m_isCopying = false; emit copyTracksDone( true ); } } bool MediaDeviceHandler::privateCopyTrackToDevice( const Meta::TrackPtr &track ) { DEBUG_BLOCK // Create new destTrack that will go into the device collection, based on source track Meta::MediaDeviceTrackPtr destTrack ( new Meta::MediaDeviceTrack( m_memColl ) ); // find path to copy to m_wc->findPathToCopy( track, destTrack ); // Create a track struct, associate it to destTrack m_wc->libCreateTrack( destTrack ); // Fill the track struct of the destTrack with info from the track parameter as source setBasicMediaDeviceTrackInfo( track, destTrack ); // set up the play url m_wc->libSetPlayableUrl( destTrack, track ); getBasicMediaDeviceTrackInfo( destTrack, destTrack ); m_trackSrcDst[ track ] = destTrack; // associate source with destination, for finalizing copy // Copy the file to the device return m_wc->libCopyTrack( track, destTrack ); } /// @param track is the source track from which we are copying void MediaDeviceHandler::slotFinalizeTrackCopy( const Meta::TrackPtr & track ) { DEBUG_BLOCK //m_tracksCopying.removeOne( track ); Meta::MediaDeviceTrackPtr destTrack = m_trackSrcDst[ track ]; // Add the track struct into the database, if the library needs to m_wc->addTrackInDB( destTrack ); // Inform subclass that a track has been added to the db m_wc->setDatabaseChanged(); // Add the new Meta::MediaDeviceTrackPtr into the device collection // add track to collection addMediaDeviceTrackToCollection( destTrack ); emit incrementProgress(); m_numTracksToCopy--; } void MediaDeviceHandler::slotCopyTrackFailed( const Meta::TrackPtr & track ) { DEBUG_BLOCK emit incrementProgress(); m_numTracksToCopy--; QString error = i18n( "The track failed to copy to the device" ); m_tracksFailed.insert( track, error ); } void MediaDeviceHandler::removeTrackListFromDevice( const Meta::TrackList &tracks ) { DEBUG_BLOCK QString removeError = i18np( "Track not deleted:", "Tracks not deleted:", tracks.size() ); QString removeErrorCaption = i18np( "Deleting Track Failed", "Deleting Tracks Failed", tracks.size() ); if ( m_isDeleting ) { KMessageBox::error( 0, i18n( "%1 tracks are already being deleted from the device.", removeError ), removeErrorCaption ); return; } if( !setupWriteCapability() ) return; m_isDeleting = true; // Init the list of tracks to be deleted m_tracksToDelete = tracks; // Set up statusbar for deletion operation Amarok::Components::logger()->newProgressOperation( this, i18np( "Removing Track from Device", "Removing Tracks from Device", tracks.size() ), tracks.size() ); m_wc->prepareToDelete(); m_numTracksToRemove = m_tracksToDelete.count(); removeNextTrackFromDevice(); } void MediaDeviceHandler::removeNextTrackFromDevice() { DEBUG_BLOCK Meta::TrackPtr track; // If there are more tracks to remove, remove the next one if( !m_tracksToDelete.isEmpty() ) { // Pop the track off the front of the list track = m_tracksToDelete.takeFirst(); // Remove the track privateRemoveTrackFromDevice( track ); } } void MediaDeviceHandler::privateRemoveTrackFromDevice( const Meta::TrackPtr &track ) { DEBUG_BLOCK Meta::MediaDeviceTrackPtr devicetrack = Meta::MediaDeviceTrackPtr::staticCast( track ); // Remove the physical file from the device, perhaps using a libcall, or KIO m_wc->libDeleteTrackFile( devicetrack ); } void MediaDeviceHandler::slotFinalizeTrackRemove( const Meta::TrackPtr & track ) { DEBUG_BLOCK Meta::MediaDeviceTrackPtr devicetrack = Meta::MediaDeviceTrackPtr::staticCast( track ); // Remove the track struct from the db, references to it m_wc->removeTrackFromDB( devicetrack ); // delete the struct associated with this track m_wc->libDeleteTrack( devicetrack ); // Inform subclass that a track has been removed from m_wc->setDatabaseChanged(); // remove from memory collection removeMediaDeviceTrackFromCollection( devicetrack ); emit incrementProgress(); m_numTracksToRemove--; if( m_numTracksToRemove == 0 ) { /* if( m_tracksFailed.size() > 0 ) { Amarok::Components::logger()->shortMessage( i18n( "%1 tracks failed to copy to the device", m_tracksFailed.size() ) ); } */ debug() << "Done removing tracks"; m_isDeleting = false; emit removeTracksDone(); } } void MediaDeviceHandler::slotDatabaseWritten( bool success ) { DEBUG_BLOCK Q_UNUSED( success ) emit endProgressOperation( this ); m_memColl->collectionUpdated(); } void MediaDeviceHandler::setupArtistMap( Meta::MediaDeviceTrackPtr track, ArtistMap &artistMap ) { const QString artist( m_rc->libGetArtist( track ) ); MediaDeviceArtistPtr artistPtr; if( artistMap.contains( artist ) ) artistPtr = MediaDeviceArtistPtr::staticCast( artistMap.value( artist ) ); else { artistPtr = MediaDeviceArtistPtr( new MediaDeviceArtist( artist ) ); artistMap.insert( artist, ArtistPtr::staticCast( artistPtr ) ); } artistPtr->addTrack( track ); track->setArtist( artistPtr ); } void MediaDeviceHandler::setupAlbumMap( Meta::MediaDeviceTrackPtr track, AlbumMap& albumMap, ArtistMap &artistMap ) { const QString album( m_rc->libGetAlbum( track ) ); QString albumArtist( m_rc->libGetAlbumArtist( track ) ); if( albumArtist.compare( "Various Artists", Qt::CaseInsensitive ) == 0 || albumArtist.compare( i18n( "Various Artists" ), Qt::CaseInsensitive ) == 0 ) { albumArtist.clear(); } MediaDeviceAlbumPtr albumPtr; if ( albumMap.contains( album, albumArtist ) ) albumPtr = MediaDeviceAlbumPtr::staticCast( albumMap.value( album, albumArtist ) ); else { MediaDeviceArtistPtr albumArtistPtr; if( artistMap.contains( albumArtist ) ) albumArtistPtr = MediaDeviceArtistPtr::staticCast( artistMap.value( albumArtist ) ); else if( !albumArtist.isEmpty() ) { albumArtistPtr = MediaDeviceArtistPtr( new MediaDeviceArtist( albumArtist ) ); artistMap.insert( albumArtist, ArtistPtr::staticCast( albumArtistPtr ) ); } albumPtr = MediaDeviceAlbumPtr( new MediaDeviceAlbum( m_memColl, album ) ); albumPtr->setAlbumArtist( albumArtistPtr ); // needs to be before albumMap.insert() albumMap.insert( AlbumPtr::staticCast( albumPtr ) ); } albumPtr->addTrack( track ); track->setAlbum( albumPtr ); bool isCompilation = albumPtr->isCompilation(); /* if at least one track from album identifies itself as a part of compilation, mark * whole album as such: (we should be deterministic wrt track adding order) */ isCompilation |= m_rc->libIsCompilation( track ); albumPtr->setIsCompilation( isCompilation ); if( albumArtist.isEmpty() ) { // set compilation flag, otherwise the album would be invisible in collection // browser if "Album Artist / Album" view is selected. albumPtr->setIsCompilation( true ); } } void MediaDeviceHandler::setupGenreMap( Meta::MediaDeviceTrackPtr track, GenreMap& genreMap ) { const QString genre = m_rc->libGetGenre( track ); MediaDeviceGenrePtr genrePtr; if ( genreMap.contains( genre ) ) genrePtr = MediaDeviceGenrePtr::staticCast( genreMap.value( genre ) ); else { genrePtr = MediaDeviceGenrePtr( new MediaDeviceGenre( genre ) ); genreMap.insert( genre, GenrePtr::staticCast( genrePtr ) ); } genrePtr->addTrack( track ); track->setGenre( genrePtr ); } void MediaDeviceHandler::setupComposerMap( Meta::MediaDeviceTrackPtr track, ComposerMap& composerMap ) { QString composer ( m_rc->libGetComposer( track ) ); MediaDeviceComposerPtr composerPtr; if ( composerMap.contains( composer ) ) composerPtr = MediaDeviceComposerPtr::staticCast( composerMap.value( composer ) ); else { composerPtr = MediaDeviceComposerPtr( new MediaDeviceComposer( composer ) ); composerMap.insert( composer, ComposerPtr::staticCast( composerPtr ) ); } composerPtr->addTrack( track ); track->setComposer( composerPtr ); } void MediaDeviceHandler::setupYearMap( Meta::MediaDeviceTrackPtr track, YearMap& yearMap ) { int year = m_rc->libGetYear( track ); MediaDeviceYearPtr yearPtr; if ( yearMap.contains( year ) ) yearPtr = MediaDeviceYearPtr::staticCast( yearMap.value( year ) ); else { yearPtr = MediaDeviceYearPtr( new MediaDeviceYear( QString::number(year) ) ); yearMap.insert( year, YearPtr::staticCast( yearPtr ) ); } yearPtr->addTrack( track ); track->setYear( yearPtr ); } bool MediaDeviceHandler::privateParseTracks() { DEBUG_BLOCK if( !setupReadCapability() ) return false; TrackMap trackMap; ArtistMap artistMap; AlbumMap albumMap; GenreMap genreMap; ComposerMap composerMap; YearMap yearMap; /* iterate through tracklist and add to appropriate map */ for( m_rc->prepareToParseTracks(); !m_rc->isEndOfParseTracksList(); m_rc->prepareToParseNextTrack() ) { /// Fetch next track to parse m_rc->nextTrackToParse(); // FIXME: should we return true or false? if (!m_memColl) return true; MediaDeviceTrackPtr track( new MediaDeviceTrack( m_memColl ) ); m_rc->setAssociateTrack( track ); getBasicMediaDeviceTrackInfo( track, track ); /* map-related info retrieval */ setupArtistMap( track, artistMap ); setupAlbumMap( track, albumMap, artistMap ); setupGenreMap( track, genreMap ); setupComposerMap( track, composerMap ); setupYearMap( track, yearMap ); /* TrackMap stuff to be subordinated later */ trackMap.insert( track->uidUrl(), TrackPtr::staticCast( track ) ); // TODO: setup titlemap for copy/deleting m_titlemap.insert( track->name(), TrackPtr::staticCast( track ) ); // TODO: abstract playlist parsing // Subscribe to Track for metadata updates subscribeTo( Meta::TrackPtr::staticCast( track ) ); } if( !m_pc && this->hasCapabilityInterface( Handler::Capability::Playlist ) ) m_pc = this->create(); if( m_pc ) { // Register the playlist provider with the playlistmanager // register a playlist provider for this type of device m_provider = new Playlists::MediaDeviceUserPlaylistProvider( m_memColl ); // Begin parsing the playlists Playlists::MediaDevicePlaylistList playlists; for( m_pc->prepareToParsePlaylists(); !m_pc->isEndOfParsePlaylistsList(); m_pc->prepareToParseNextPlaylist() ) { m_pc->nextPlaylistToParse(); if( m_pc->shouldNotParseNextPlaylist() ) continue; // Create a new track list Meta::TrackList tracklist; for ( m_pc->prepareToParsePlaylistTracks(); !(m_pc->isEndOfParsePlaylist()); m_pc->prepareToParseNextPlaylistTrack() ) { m_pc->nextPlaylistTrackToParse(); // Grab the track associated with the next struct Meta::TrackPtr track = Meta::TrackPtr::staticCast( m_pc->libGetTrackPtrForTrackStruct() ); // if successful, add it into the list at the end. // it is assumed that the list has some presorted order // and this is left to the library if ( track ) tracklist << track; } // Make a playlist out of this tracklist Playlists::MediaDevicePlaylistPtr playlist( new Playlists::MediaDevicePlaylist( m_pc->libGetPlaylistName(), tracklist ) ); m_pc->setAssociatePlaylist( playlist ); // Insert the new playlist into the list of playlists //playlists << playlist; // Inform the provider of the new playlist m_provider->addMediaDevicePlaylist( playlist ); } // When the provider saves a playlist, the handler should save it internally connect( m_provider, SIGNAL(playlistSaved(Playlists::MediaDevicePlaylistPtr,QString)), SLOT(savePlaylist(Playlists::MediaDevicePlaylistPtr,QString)) ); connect( m_provider, SIGNAL(playlistRenamed(Playlists::MediaDevicePlaylistPtr)), SLOT(renamePlaylist(Playlists::MediaDevicePlaylistPtr)) ); connect( m_provider, SIGNAL(playlistsDeleted(Playlists::MediaDevicePlaylistList)), SLOT(deletePlaylists(Playlists::MediaDevicePlaylistList)) ); The::playlistManager()->addProvider( m_provider, m_provider->category() ); m_provider->sendUpdated(); } if( !m_podcastCapability && hasCapabilityInterface( Handler::Capability::Podcast ) ) { } // Finally, assign the created maps to the collection m_memColl->memoryCollection()->acquireWriteLock(); m_memColl->memoryCollection()->setTrackMap( trackMap ); m_memColl->memoryCollection()->setArtistMap( artistMap ); m_memColl->memoryCollection()->setAlbumMap( albumMap ); m_memColl->memoryCollection()->setGenreMap( genreMap ); m_memColl->memoryCollection()->setComposerMap( composerMap ); m_memColl->memoryCollection()->setYearMap( yearMap ); m_memColl->memoryCollection()->releaseLock(); m_memColl->collectionUpdated(); return true; } void MediaDeviceHandler::slotCopyNextTrackFailed( ThreadWeaver::Job* job, const Meta::TrackPtr& track ) { Q_UNUSED( job ); enqueueNextCopyThread(); m_copyFailed = true; slotCopyTrackFailed( track ); } void MediaDeviceHandler::slotCopyNextTrackDone( ThreadWeaver::Job* job, const Meta::TrackPtr& track ) { Q_UNUSED( track ) enqueueNextCopyThread(); if ( job->success() ) slotFinalizeTrackCopy( track ); else { m_copyFailed = true; slotCopyTrackFailed( track ); } } void MediaDeviceHandler::enqueueNextCopyThread() { Meta::TrackPtr track; // If there are more tracks to copy, copy the next one if( !m_tracksToCopy.isEmpty() ) { // Pop the track off the front of the list track = m_tracksToCopy.first(); m_tracksToCopy.removeFirst(); // Copy the track ThreadWeaver::Weaver::instance()->enqueue( new CopyWorkerThread( track, this ) ); } else { // Finish the progress bar emit incrementProgress(); emit endProgressOperation( this ); // Inform CollectionLocation that copying is done m_isCopying = false; emit copyTracksDone( true ); } } float MediaDeviceHandler::freeSpace() { if ( setupReadCapability() ) return m_rc->totalCapacity() - m_rc->usedCapacity(); else return 0.0; } float MediaDeviceHandler::usedcapacity() { if ( setupReadCapability() ) return m_rc->usedCapacity(); else return 0.0; } float MediaDeviceHandler::totalcapacity() { if ( setupReadCapability() ) return m_rc->totalCapacity(); else return 0.0; } Playlists::UserPlaylistProvider* MediaDeviceHandler::provider() { DEBUG_BLOCK return (qobject_cast( m_provider ) ); } void MediaDeviceHandler::savePlaylist( const Playlists::MediaDevicePlaylistPtr &playlist, const QString& name ) { DEBUG_BLOCK if( !m_pc ) { if( this->hasCapabilityInterface( Handler::Capability::Playlist ) ) { m_pc = this->create(); if( !m_pc ) { debug() << "Handler does not have MediaDeviceHandler::PlaylistCapability."; } } } if( m_pc ) { m_pc->savePlaylist( playlist, name ); writeDatabase(); } } void MediaDeviceHandler::renamePlaylist( const Playlists::MediaDevicePlaylistPtr &playlist ) { DEBUG_BLOCK if( !m_pc ) { if( this->hasCapabilityInterface( Handler::Capability::Playlist ) ) { m_pc = this->create(); if( !m_pc ) { debug() << "Handler does not have MediaDeviceHandler::PlaylistCapability."; } } } if( m_pc ) { debug() << "Renaming playlist"; m_pc->renamePlaylist( playlist ); writeDatabase(); } } void MediaDeviceHandler::deletePlaylists( const Playlists::MediaDevicePlaylistList &playlistlist ) { DEBUG_BLOCK if( !m_pc ) { if( this->hasCapabilityInterface( Handler::Capability::Playlist ) ) { m_pc = this->create(); if( !m_pc ) { debug() << "Handler does not have MediaDeviceHandler::PlaylistCapability."; } } } if( m_pc ) { debug() << "Deleting playlists"; foreach( Playlists::MediaDevicePlaylistPtr playlist, playlistlist ) { m_pc->deletePlaylist( playlist ); } writeDatabase(); } } bool MediaDeviceHandler::setupReadCapability() { if( m_rc ) return true; if( !hasCapabilityInterface( Handler::Capability::Readable ) ) return false; m_rc = create(); return (bool) m_rc; } bool MediaDeviceHandler::setupWriteCapability() { if( m_wc ) return true; if( !hasCapabilityInterface( Handler::Capability::Writable ) ) return false; m_wc = create(); return (bool) m_wc; } /** Observer Methods **/ void MediaDeviceHandler::metadataChanged( TrackPtr track ) { DEBUG_BLOCK Meta::MediaDeviceTrackPtr trackPtr = Meta::MediaDeviceTrackPtr::staticCast( track ); if( !setupWriteCapability() ) return; setBasicMediaDeviceTrackInfo( track, trackPtr ); m_wc->setDatabaseChanged(); m_wc->updateTrack( trackPtr ); } void MediaDeviceHandler::metadataChanged( ArtistPtr artist ) { Q_UNUSED( artist ); } void MediaDeviceHandler::metadataChanged( AlbumPtr album ) { Q_UNUSED( album ); } void MediaDeviceHandler::metadataChanged( GenrePtr genre ) { Q_UNUSED( genre ); } void MediaDeviceHandler::metadataChanged( ComposerPtr composer ) { Q_UNUSED( composer ); } void MediaDeviceHandler::metadataChanged( YearPtr year ) { Q_UNUSED( year ); } void MediaDeviceHandler::parseTracks() { ThreadWeaver::Weaver::instance()->enqueue( new ParseWorkerThread( this ) ); } // ParseWorkerThread ParseWorkerThread::ParseWorkerThread( MediaDeviceHandler* handler ) : ThreadWeaver::Job() , m_success( false ) , m_handler( handler ) { connect( this, SIGNAL(done(ThreadWeaver::Job*)), this, SLOT(slotDoneSuccess(ThreadWeaver::Job*)) ); } ParseWorkerThread::~ParseWorkerThread() { //nothing to do } bool ParseWorkerThread::success() const { return m_success; } void ParseWorkerThread::run() { m_success = m_handler->privateParseTracks(); } void ParseWorkerThread::slotDoneSuccess( ThreadWeaver::Job* ) { if (m_handler->m_memColl) m_handler->m_memColl->emitCollectionReady(); } // CopyWorkerThread CopyWorkerThread::CopyWorkerThread( const Meta::TrackPtr &track, MediaDeviceHandler* handler ) : ThreadWeaver::Job() , m_success( false ) , m_track( track ) , m_handler( handler ) { //connect( this, SIGNAL(done(ThreadWeaver::Job*)), m_handler, SLOT(slotCopyNextTrackToDevice(ThreadWeaver::Job*)), Qt::QueuedConnection ); connect( this, SIGNAL(failed(ThreadWeaver::Job*)), this, SLOT(slotDoneFailed(ThreadWeaver::Job*)), Qt::QueuedConnection ); connect( this, SIGNAL(copyTrackFailed(ThreadWeaver::Job*,Meta::TrackPtr)), m_handler, SLOT(slotCopyNextTrackFailed(ThreadWeaver::Job*,Meta::TrackPtr)) ); connect( this, SIGNAL(copyTrackDone(ThreadWeaver::Job*,Meta::TrackPtr)), m_handler, SLOT(slotCopyNextTrackDone(ThreadWeaver::Job*,Meta::TrackPtr)) ); connect( this, SIGNAL(done(ThreadWeaver::Job*)), this, SLOT(slotDoneSuccess(ThreadWeaver::Job*)) ); //connect( this, SIGNAL(done(ThreadWeaver::Job*)), this, SLOT(deleteLater()) ); } CopyWorkerThread::~CopyWorkerThread() { //nothing to do } bool CopyWorkerThread::success() const { return m_success; } void CopyWorkerThread::run() { m_success = m_handler->privateCopyTrackToDevice( m_track ); } void CopyWorkerThread::slotDoneSuccess( ThreadWeaver::Job* ) { emit copyTrackDone( this, m_track ); } void CopyWorkerThread::slotDoneFailed( ThreadWeaver::Job* ) { emit copyTrackFailed( this, m_track ); } diff --git a/src/core-impl/collections/mediadevicecollection/handler/MediaDeviceHandler.h b/src/core-impl/collections/mediadevicecollection/handler/MediaDeviceHandler.h index 36cc930ad6..9b45e2d92c 100644 --- a/src/core-impl/collections/mediadevicecollection/handler/MediaDeviceHandler.h +++ b/src/core-impl/collections/mediadevicecollection/handler/MediaDeviceHandler.h @@ -1,490 +1,490 @@ /**************************************************************************************** * Copyright (c) 2009 Alejandro Wainzinger * * * * 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) version 3 or * * any later version accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * 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, see . * ****************************************************************************************/ #ifndef MEDIADEVICEHANDLER_H #define MEDIADEVICEHANDLER_H #include "core/meta/Observer.h" #include "core-impl/collections/mediadevicecollection/MediaDeviceMeta.h" #include "core-impl/collections/mediadevicecollection/handler/MediaDeviceHandlerCapability.h" #include "core-impl/collections/mediadevicecollection/handler/capabilities/PlaylistCapability.h" #include "core-impl/collections/mediadevicecollection/handler/capabilities/PodcastCapability.h" #include "core-impl/collections/mediadevicecollection/handler/capabilities/ReadCapability.h" #include "core-impl/collections/mediadevicecollection/handler/capabilities/WriteCapability.h" #include "core-impl/collections/mediadevicecollection/playlist/MediaDevicePlaylist.h" #include "core-impl/collections/mediadevicecollection/playlist/MediaDeviceUserPlaylistProvider.h" #include "core-impl/collections/support/MemoryCollection.h" #include "core-impl/playlists/providers/user/UserPlaylistProvider.h" #include #include #include #include #include class QString; class QMutex; namespace Collections { class MediaDeviceCollection; } namespace Meta { typedef QMultiMap TitleMap; class MEDIADEVICECOLLECTION_EXPORT MetaHandlerCapability { public: virtual ~MetaHandlerCapability() {} virtual bool hasCapabilityInterface( Handler::Capability::Type type ) const; virtual Handler::Capability* createCapabilityInterface( Handler::Capability::Type type ); /** * Retrieves a specialized interface which represents a capability of this * object. * * @returns a pointer to the capability interface if it exists, 0 otherwise */ template CapIface *create() { Handler::Capability::Type type = CapIface::capabilityInterfaceType(); Handler::Capability *iface = createCapabilityInterface(type); return qobject_cast(iface); } /** * Tests if an object provides a given capability interface. * * @returns true if the interface is available, false otherwise */ template bool is() const { return hasCapabilityInterface( CapIface::capabilityInterfaceType() ); } }; /** The MediaDeviceHandler is the backend where all low-level library calls are made. It exists to leave a generic API in the other classes, while allowing for low-level calls to be isolated here. */ class MEDIADEVICECOLLECTION_EXPORT MediaDeviceHandler : public QObject, public Meta::MetaHandlerCapability, public Meta::Observer { Q_OBJECT public: /** * Destructor */ virtual ~MediaDeviceHandler(); // Declare thread as friend class friend class ParseWorkerThread; /** * Begins an attempt to connect to the device, and emits * attemptConnectionDone when it finishes. */ virtual void init() = 0; // collection /** * Checks if the handler successfully connected * to the device. * @return * TRUE if the device was successfully connected to * FALSE if the device was not successfully connected to */ bool succeeded() const // collection { return m_success; } /// Methods provided for CollectionLocation /** * Checks if a device can be written to. * @return * TRUE if the device can be written to * FALSE if the device can not be written to */ virtual bool isWritable() const = 0; /** Given a list of tracks, get URLs for device tracks * of this type of device. If the device needs to * do some work to get URLs (e.g. copy tracks to a * temporary location) the overridden method in * the handler takes care of it, but must emit * gotCopyableUrls when finished. * @param tracks The list of tracks for which to fetch urls */ virtual void getCopyableUrls( const Meta::TrackList &tracks ); /** * Fetches the human-readable name of the device. * This is often called from the Collection since * a library call is needed to get this name. * @return A QString with the name */ virtual QString prettyName() const = 0; /** * Copies a list of tracks to the device. * @param tracklist The list of tracks to copy. */ void copyTrackListToDevice( const Meta::TrackList tracklist ); /** * Removes a list of tracks from the device. * @param tracklist The list of tracks to remove. */ void removeTrackListFromDevice( const Meta::TrackList &tracks ); /** This function is called just before a track in the playlist is to be played, and gives * a chance for e.g. MTP to copy the track to a temporary location, set a playable url, * to emulate the track actually being played off the device * @param track The track that needs to prepare to be played */ virtual void prepareToPlay( Meta::MediaDeviceTrackPtr &track ) { Q_UNUSED( track ) } // called by @param track virtual float usedcapacity(); virtual float totalcapacity(); Playlists::UserPlaylistProvider* provider(); // HACK: Used for device-specific actions, such as initialize for iPod virtual QList collectionActions() { return QList (); } signals: - void gotCopyableUrls( const QMap &urls ); + void gotCopyableUrls( const QMap &urls ); void databaseWritten( bool succeeded ); void deleteTracksDone(); void incrementProgress(); void endProgressOperation( QObject *owner ); void copyTracksDone( bool success ); void removeTracksDone(); /* File I/O Methods */ public slots: /** * Parses the media device's database and creates a Meta::MediaDeviceTrack * for each track in the database. NOTE: only call once per device. */ void parseTracks(); // collection /** * Writes to the device's database if it has one, otherwise * simply calls slotDatabaseWritten to continue the workflow. */ virtual void writeDatabase() { slotDatabaseWritten( true ); } void savePlaylist( const Playlists::MediaDevicePlaylistPtr &playlist, const QString& name ); void renamePlaylist( const Playlists::MediaDevicePlaylistPtr &playlist ); void deletePlaylists( const Playlists::MediaDevicePlaylistList &playlistlist ); bool privateParseTracks(); void copyNextTrackToDevice(); bool privateCopyTrackToDevice( const Meta::TrackPtr& track ); void removeNextTrackFromDevice(); void privateRemoveTrackFromDevice( const Meta::TrackPtr &track ); protected: /** * Constructor * @param parent the Collection whose handler this is */ MediaDeviceHandler( QObject *parent ); /** * Creates a MediaDeviceTrack based on the latest track struct created as a * result of a copy to the device, and adds it into the collection to reflect * that it has been copied. * @param track The track to add to the collection */ void addMediaDeviceTrackToCollection( Meta::MediaDeviceTrackPtr &track ); /** * Removes the @param track from all the collection's maps to reflect that * it has been removed from the collection * @param track The track to remove from the collection */ void removeMediaDeviceTrackFromCollection( Meta::MediaDeviceTrackPtr &track ); /** * Uses wrapped libGet methods to fill a track with information from device * @param track The track from whose associated struct to get the information * @param destTrack The track that we want to fill with information */ void getBasicMediaDeviceTrackInfo( const Meta::MediaDeviceTrackPtr& track, Meta::MediaDeviceTrackPtr destTrack ); /** * Uses wrapped libSet methods to fill a track struct of the particular library * with information from a Meta::Track * @param srcTrack The track that has the source information * @param destTrack The track whose associated struct we want to fill with information */ void setBasicMediaDeviceTrackInfo( const Meta::TrackPtr &srcTrack, Meta::MediaDeviceTrackPtr destTrack ); Collections::MediaDeviceCollection *m_memColl; ///< Associated collection bool m_success; bool m_copyingthreadsafe; ///< whether or not the handler's method of copying is threadsafe TitleMap m_titlemap; ///< Map of track titles to tracks, used to detect duplicates protected slots: void slotCopyNextTrackFailed( ThreadWeaver::Job* job, const Meta::TrackPtr& track ); void slotCopyNextTrackDone( ThreadWeaver::Job* job, const Meta::TrackPtr& track ); void slotFinalizeTrackCopy( const Meta::TrackPtr & track ); void slotCopyTrackFailed( const Meta::TrackPtr & track ); void slotFinalizeTrackRemove( const Meta::TrackPtr & track ); void slotDatabaseWritten( bool success ); void enqueueNextCopyThread(); void slotDeletingHandler(); private: /** * Pulls out meta information (e.g. artist string) * from track struct, inserts into appropriate map * (e.g. ArtistMap). Sets track's meta info * (e.g. artist string) to that extracted from * track struct's. * @param track - track being written to * @param Map - map where meta information is * associated to appropriate meta pointer * (e.g. QString artist, ArtistPtr ) */ void setupArtistMap( Meta::MediaDeviceTrackPtr track, ArtistMap &artistMap ); void setupAlbumMap( Meta::MediaDeviceTrackPtr track, AlbumMap &albumMap, ArtistMap &artistMap ); void setupGenreMap( Meta::MediaDeviceTrackPtr track, GenreMap &genreMap ); void setupComposerMap( Meta::MediaDeviceTrackPtr track, ComposerMap &composerMap ); void setupYearMap( Meta::MediaDeviceTrackPtr track, YearMap &yearMap ); // Misc. Helper Methods /** * Tries to create read capability in m_rc * @return true if m_rc is valid read capability, false otherwise */ bool setupReadCapability(); /** * Tries to create write capability in m_rc * @return true if m_wc is valid write capability, false otherwise */ bool setupWriteCapability(); /** * @return free space on the device */ float freeSpace(); // Observer Methods /** These methods are called when the metadata of a track has changed. They invoke an MediaDevice DB update */ virtual void metadataChanged( Meta::TrackPtr track ); virtual void metadataChanged( Meta::ArtistPtr artist ); virtual void metadataChanged( Meta::AlbumPtr album ); virtual void metadataChanged( Meta::GenrePtr genre ); virtual void metadataChanged( Meta::ComposerPtr composer ); virtual void metadataChanged( Meta::YearPtr year ); /** * Handler Variables */ Playlists::MediaDeviceUserPlaylistProvider *m_provider; ///< Associated playlist provider bool m_copyFailed; ///< Indicates whether a copy failed or not bool m_isCopying; bool m_isDeleting; Meta::TrackList m_tracksToCopy; ///< List of tracks left to copy Meta::TrackList m_tracksCopying; ///< List of tracks currrently copying Meta::TrackList m_tracksToDelete; ///< List of tracks left to delete int m_numTracksToCopy; ///< The number of tracks left to copy int m_numTracksToRemove; ///< The number of tracks left to remove QMap m_tracksFailed; ///< tracks that failed to copy QHash m_trackSrcDst; ///< points source to destTracks, for completion of addition to collection QMutex m_mutex; ///< A make certain operations atomic when threads are at play // Capability-related variables Handler::PlaylistCapability *m_pc; Handler::PodcastCapability *m_podcastCapability; Handler::ReadCapability *m_rc; Handler::WriteCapability *m_wc; }; /** * The ParseWorkerThread is used to run a full parse of the device's database in * a separate thread. Once done, it informs the Collection it is done */ class ParseWorkerThread : public ThreadWeaver::Job { Q_OBJECT public: /** * The constructor. * @param handler The handler */ ParseWorkerThread( MediaDeviceHandler* handler); /** * The destructor. */ virtual ~ParseWorkerThread(); /** * Sees the success variable, which says whether or not the copy completed successfully. * @return Whether or not the copy was successful, i.e. m_success */ virtual bool success() const; signals: private slots: /** * Is called when the job is done successfully, and simply * calls Collection's emitCollectionReady() * @param job The job that was done */ void slotDoneSuccess( ThreadWeaver::Job* ); protected: /** * Reimplemented, simply runs the parse method. */ virtual void run(); private: bool m_success; ///< Whether or not the parse was successful MediaDeviceHandler *m_handler; ///< The handler }; /** * The CopyWorkerThread is used to run a copy operation on a single track in a separate thread. * Copying is generally done one thread at a time so as to not hurt performance, and because * many copying mechanisms like that of libmtp can only copy one file at a time. Copying * methods that are not threadsafe should not use CopyWorkerThread, and should set the * Handler's m_copyingthreadsafe variable to false in the Handler's constructor. */ class CopyWorkerThread : public ThreadWeaver::Job { Q_OBJECT public: /** * The constructor. * @param track The source track to copy from * @param handler The handler */ CopyWorkerThread( const Meta::TrackPtr &track, MediaDeviceHandler* handler ); /** * The destructor. */ virtual ~CopyWorkerThread(); /** * Sets the success variable, which says whether or not the copy completed successfully. * @return Whether or not the copy was successful, i.e. m_success */ virtual bool success() const; signals: /** * Is emitted when the job is done successfully * @param job The job that was done * @param track The source track used for the copy */ void copyTrackDone( ThreadWeaver::Job*, const Meta::TrackPtr& track ); /** * Is emitted when the job is done and has failed * @param job The job that was done * @param track The source track used for the copy */ void copyTrackFailed( ThreadWeaver::Job*, const Meta::TrackPtr& track ); private slots: /** * Is called when the job is done successfully, and simply * emits copyTrackDone * @param job The job that was done */ void slotDoneSuccess( ThreadWeaver::Job* ); /** * Is called when the job is done and failed, and simply * emits copyTrackFailed * @param job The job that was done */ void slotDoneFailed( ThreadWeaver::Job* ); protected: /** * Reimplemented, simply runs the copy track method. */ virtual void run(); private: bool m_success; ///< Whether or not the copy was successful Meta::TrackPtr m_track; ///< The source track to copy from MediaDeviceHandler *m_handler; ///< The handler }; } #endif diff --git a/src/core-impl/collections/mediadevicecollection/handler/capabilities/ReadCapability.h b/src/core-impl/collections/mediadevicecollection/handler/capabilities/ReadCapability.h index 5d87d85de7..70d0717d53 100644 --- a/src/core-impl/collections/mediadevicecollection/handler/capabilities/ReadCapability.h +++ b/src/core-impl/collections/mediadevicecollection/handler/capabilities/ReadCapability.h @@ -1,126 +1,126 @@ /**************************************************************************************** * Copyright (c) 2009 Alejandro Wainzinger * * Copyright (c) 2011 MatÄ›j Laitl * * * * 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, see . * ****************************************************************************************/ #ifndef MEDIADEVICEHANDLER_READCAPABILITY_H #define MEDIADEVICEHANDLER_READCAPABILITY_H #include "core-impl/collections/mediadevicecollection/MediaDeviceMeta.h" #include "core-impl/collections/mediadevicecollection/handler/MediaDeviceHandlerCapability.h" #include "core-impl/collections/mediadevicecollection/support/mediadevicecollection_export.h" #include namespace Handler { class MEDIADEVICECOLLECTION_EXPORT ReadCapability : public Handler::Capability { Q_OBJECT public: ReadCapability( QObject *parent ) : Capability( parent ) {} virtual ~ReadCapability(); static Type capabilityInterfaceType() { return Handler::Capability::Readable; } /* Parsing of Tracks on Device */ /** * Initializes iteration over some list of track structs * e.g. with libgpod, this initializes a GList to the beginning of * the list of tracks */ virtual void prepareToParseTracks() = 0; /** * Runs a test to see if we have reached the end of * the list of tracks to be parsed on the device, e.g. in libgpod * this tests if cur != NULL, i.e. if(cur) */ virtual bool isEndOfParseTracksList() = 0; /** * Moves the iterator to the next track on the list of * track structs, e.g. with libgpod, cur = cur->next where cur * is a GList* */ virtual void prepareToParseNextTrack() = 0; /** * This method attempts to access the special struct of the * next track, so that information can then be parsed from it. * For libgpod, this is m_currtrack = (Itdb_Track*) cur->data */ virtual void nextTrackToParse() = 0; /** * This method must create a two-way association of the current Meta::Track * to the special struct provided by the library to read/write information. * For example, for libgpod one would associate Itdb_Track*. It makes * the most sense to use a QHash since it is fastest lookup and order * does not matter. * @param track The track to two-way associate with a library track struct */ virtual void setAssociateTrack( const Meta::MediaDeviceTrackPtr track ) = 0; /* * Methods that wrap get/set of information using given library (e.g. libgpod) * Subclasses of MediaDeviceHandler must keep a pointer to the track struct * associated to the track parameter to get the information from the struct in libGet*, * and to set the struct's information to the passed metadata in libSet* */ virtual QString libGetAlbum( const Meta::MediaDeviceTrackPtr &track ) = 0; virtual QString libGetArtist( const Meta::MediaDeviceTrackPtr &track ) = 0; virtual QString libGetAlbumArtist( const Meta::MediaDeviceTrackPtr &track ) = 0; virtual QString libGetComposer( const Meta::MediaDeviceTrackPtr &track ) = 0; virtual QString libGetGenre( const Meta::MediaDeviceTrackPtr &track ) = 0; virtual int libGetYear( const Meta::MediaDeviceTrackPtr &track ) = 0; virtual QString libGetTitle( const Meta::MediaDeviceTrackPtr &track ) = 0; virtual qint64 libGetLength( const Meta::MediaDeviceTrackPtr &track ) = 0; virtual int libGetTrackNumber( const Meta::MediaDeviceTrackPtr &track ) = 0; virtual QString libGetComment( const Meta::MediaDeviceTrackPtr &track ) = 0; virtual int libGetDiscNumber( const Meta::MediaDeviceTrackPtr &track ) = 0; virtual int libGetBitrate( const Meta::MediaDeviceTrackPtr &track ) = 0; virtual int libGetSamplerate( const Meta::MediaDeviceTrackPtr &track ) = 0; virtual qreal libGetBpm( const Meta::MediaDeviceTrackPtr &track ) = 0; virtual int libGetFileSize( const Meta::MediaDeviceTrackPtr &track ) = 0; virtual int libGetPlayCount( const Meta::MediaDeviceTrackPtr &track ) = 0; virtual QDateTime libGetLastPlayed( const Meta::MediaDeviceTrackPtr &track ) = 0; virtual int libGetRating( const Meta::MediaDeviceTrackPtr &track ) = 0; virtual QString libGetType( const Meta::MediaDeviceTrackPtr &track ) = 0; - virtual KUrl libGetPlayableUrl( const Meta::MediaDeviceTrackPtr &track ) = 0; + virtual QUrl libGetPlayableUrl( const Meta::MediaDeviceTrackPtr &track ) = 0; virtual bool libIsCompilation( const Meta::MediaDeviceTrackPtr &track ); virtual qreal libGetReplayGain( const Meta::MediaDeviceTrackPtr &track ); /** * Get used capacity on the device, in bytes. Returns 0.0 if capacity information * is not appropriate or not available. */ virtual float usedCapacity() const { return 0.0; } /** * Get total (used + free) capacity on the device, in bytes. Returns 0.0 if * capacity information is not appropriate or not available. */ virtual float totalCapacity() const { return 0.0; } }; } #endif diff --git a/src/core-impl/collections/mediadevicecollection/playlist/MediaDevicePlaylist.h b/src/core-impl/collections/mediadevicecollection/playlist/MediaDevicePlaylist.h index acfa4cceb8..e3856f63b1 100644 --- a/src/core-impl/collections/mediadevicecollection/playlist/MediaDevicePlaylist.h +++ b/src/core-impl/collections/mediadevicecollection/playlist/MediaDevicePlaylist.h @@ -1,62 +1,62 @@ /**************************************************************************************** * Copyright (c) 2009 Alejandro Wainzinger * * * * 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, see . * ****************************************************************************************/ #ifndef METAMEDIADEVICEPLAYLIST_H #define METAMEDIADEVICEPLAYLIST_H #include "core/playlists/Playlist.h" #include "core/support/Debug.h" namespace Playlists { class MediaDevicePlaylist; typedef KSharedPtr MediaDevicePlaylistPtr; typedef QList MediaDevicePlaylistList; class MediaDevicePlaylist : public Playlist { public: MediaDevicePlaylist( const QString &name, const Meta::TrackList &tracks ); ~MediaDevicePlaylist(); // Playlist Functions virtual QString name() const { return m_name; } - virtual KUrl uidUrl() const { return KUrl(); } + virtual QUrl uidUrl() const { return QUrl(); } /**override showing just the filename */ virtual void setName( const QString &name ); virtual int trackCount() const; virtual Meta::TrackList tracks(); virtual void addTrack( Meta::TrackPtr track, int position = -1 ); virtual void removeTrack( int position ); private: Meta::TrackList m_tracks; QString m_name; }; } Q_DECLARE_METATYPE( Playlists::MediaDevicePlaylistPtr ) Q_DECLARE_METATYPE( Playlists::MediaDevicePlaylistList ) #endif diff --git a/src/core-impl/collections/mediadevicecollection/playlist/MediaDeviceUserPlaylistProvider.cpp b/src/core-impl/collections/mediadevicecollection/playlist/MediaDeviceUserPlaylistProvider.cpp index e04639975f..e0f20d7644 100644 --- a/src/core-impl/collections/mediadevicecollection/playlist/MediaDeviceUserPlaylistProvider.cpp +++ b/src/core-impl/collections/mediadevicecollection/playlist/MediaDeviceUserPlaylistProvider.cpp @@ -1,162 +1,162 @@ /**************************************************************************************** * Copyright (c) 2008 Nikolaj Hald Nielsen * * Copyright (c) 2008 Bart Cerneels * * * * 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, see . * ****************************************************************************************/ #include "MediaDeviceUserPlaylistProvider.h" #include "SvgHandler.h" #include "browsers/playlistbrowser/UserPlaylistModel.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include "core-impl/collections/mediadevicecollection/MediaDeviceCollection.h" #include "core-impl/collections/support/CollectionManager.h" #include "core-impl/playlists/types/file/PlaylistFileSupport.h" #include "core-impl/playlists/types/file/m3u/M3UPlaylist.h" #include "core-impl/playlists/types/file/pls/PLSPlaylist.h" #include "core-impl/playlists/types/file/xspf/XSPFPlaylist.h" #include "playlistmanager/PlaylistManager.h" #include #include -#include +#include #include static const int USERPLAYLIST_DB_VERSION = 2; static const QString key("AMAROK_USERPLAYLIST"); namespace Playlists { MediaDeviceUserPlaylistProvider::MediaDeviceUserPlaylistProvider( Collections::MediaDeviceCollection *collection ) : Playlists::UserPlaylistProvider() , m_collection( collection ) { DEBUG_BLOCK // checkTables(); // m_root = Playlists::MediaDevicePlaylistGroupPtr( new Playlists::MediaDevicePlaylistGroup( "", // Playlists::MediaDevicePlaylistGroupPtr() ) ); // The::playlistManager()->addProvider( this, category() ); } MediaDeviceUserPlaylistProvider::~MediaDeviceUserPlaylistProvider() { DEBUG_BLOCK // foreach( Playlists::MediaDevicePlaylistPtr playlist, m_playlists ) // { // playlist->saveToDb( true ); // } m_playlists.clear(); // emit updated(); // The::playlistManager()->removeProvider( this ); } Playlists::PlaylistList MediaDeviceUserPlaylistProvider::playlists() { DEBUG_BLOCK Playlists::PlaylistList playlists; foreach( Playlists::MediaDevicePlaylistPtr mediadevicePlaylist, m_playlists ) { playlists << Playlists::PlaylistPtr::staticCast( mediadevicePlaylist ); } return playlists; } Playlists::PlaylistPtr MediaDeviceUserPlaylistProvider::save( const Meta::TrackList &tracks ) { DEBUG_BLOCK // This provider can only save it's own tracks for now, filter out all the others. Meta::TrackList filteredTracks; foreach( const Meta::TrackPtr track, tracks ) if( track->collection() == m_collection ) filteredTracks << track; return save( filteredTracks, QDateTime::currentDateTime().toString( "ddd MMMM d yy hh-mm" ) ); } Playlists::PlaylistPtr MediaDeviceUserPlaylistProvider::save( const Meta::TrackList &tracks, const QString& name ) { DEBUG_BLOCK debug() << "saving " << tracks.count() << " tracks to device with name" << name; // NOTE: the playlist constructor tells the handler to make the playlist, save to db etc. Playlists::MediaDevicePlaylistPtr pl = Playlists::MediaDevicePlaylistPtr( new Playlists::MediaDevicePlaylist( name, tracks ) ); //pl = 0; emit playlistSaved( pl, name ); // inform handler of new playlist addMediaDevicePlaylist( pl ); return Playlists::PlaylistPtr::dynamicCast( pl ); } void MediaDeviceUserPlaylistProvider::renamePlaylist( Playlists::PlaylistPtr playlist, const QString &newName ) { DEBUG_BLOCK Playlists::MediaDevicePlaylistPtr pl = Playlists::MediaDevicePlaylistPtr::staticCast( playlist ); if( pl ) { debug() << "Setting name of playlist"; pl->setName( newName ); emit playlistRenamed( pl ); } } bool MediaDeviceUserPlaylistProvider::deletePlaylists( const Playlists::PlaylistList &playlistlist ) { Playlists::MediaDevicePlaylistList pllist; foreach( Playlists::PlaylistPtr playlist, playlistlist ) { Playlists::MediaDevicePlaylistPtr pl = Playlists::MediaDevicePlaylistPtr::staticCast( playlist ); if( pl ) { debug() << "Deleting playlist: " << pl->name(); removePlaylist( pl ); pllist << pl; } } emit playlistsDeleted( pllist ); return true; } void MediaDeviceUserPlaylistProvider::addMediaDevicePlaylist( Playlists::MediaDevicePlaylistPtr &playlist ) { m_playlists << playlist; emit updated(); } void MediaDeviceUserPlaylistProvider::removePlaylist( Playlists::MediaDevicePlaylistPtr &playlist ) { m_playlists.removeOne( playlist ); emit updated(); } } //namespace Playlists diff --git a/src/core-impl/collections/mediadevicecollection/podcast/MediaDevicePodcastProvider.cpp b/src/core-impl/collections/mediadevicecollection/podcast/MediaDevicePodcastProvider.cpp index 785063ff8c..1e897c51c1 100644 --- a/src/core-impl/collections/mediadevicecollection/podcast/MediaDevicePodcastProvider.cpp +++ b/src/core-impl/collections/mediadevicecollection/podcast/MediaDevicePodcastProvider.cpp @@ -1,107 +1,107 @@ /**************************************************************************************** * Copyright 2010 Bart Cerneels * * * * 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, see . * ****************************************************************************************/ #include "MediaDevicePodcastProvider.h" using namespace Podcasts; MediaDevicePodcastProvider::MediaDevicePodcastProvider( Meta::MediaDeviceHandler *handler ) : m_handler( handler ) { } void -MediaDevicePodcastProvider::addPodcast( const KUrl &url ) +MediaDevicePodcastProvider::addPodcast( const QUrl &url ) { Q_UNUSED( url ) //can this handler even fetch feeds itself? } PodcastChannelPtr MediaDevicePodcastProvider::addChannel( PodcastChannelPtr channel ) { Q_UNUSED( channel ) return PodcastChannelPtr(); } PodcastEpisodePtr MediaDevicePodcastProvider::addEpisode( PodcastEpisodePtr episode ) { Q_UNUSED( episode ) return PodcastEpisodePtr(); } PodcastChannelList MediaDevicePodcastProvider::channels() { PodcastChannelList channels; return channels; } void MediaDevicePodcastProvider::removeSubscription( PodcastChannelPtr channel ) { Q_UNUSED( channel ) } void MediaDevicePodcastProvider::configureProvider() { } void MediaDevicePodcastProvider::configureChannel( PodcastChannelPtr channel ) { Q_UNUSED( channel ) } QString MediaDevicePodcastProvider::prettyName() const { return i18nc( "Podcasts on a media device", "Podcasts on %1", m_handler->prettyName() ); } Playlists::PlaylistList MediaDevicePodcastProvider::playlists() { Playlists::PlaylistList playlists; foreach( PodcastChannelPtr channel, channels() ) playlists << Playlists::PlaylistPtr::dynamicCast( channel ); return playlists; } Playlists::PlaylistPtr MediaDevicePodcastProvider::addPlaylist( Playlists::PlaylistPtr playlist ) { PodcastChannelPtr channel = PodcastChannelPtr::dynamicCast( playlist ); if( channel.isNull() ) return Playlists::PlaylistPtr(); return Playlists::PlaylistPtr::dynamicCast( addChannel( channel ) ); } Meta::TrackPtr MediaDevicePodcastProvider::addTrack( Meta::TrackPtr track ) { PodcastEpisodePtr episode = PodcastEpisodePtr::dynamicCast( track ); if( episode.isNull() ) return Meta::TrackPtr(); return Meta::TrackPtr::dynamicCast( addEpisode( episode ) ); } diff --git a/src/core-impl/collections/mediadevicecollection/podcast/MediaDevicePodcastProvider.h b/src/core-impl/collections/mediadevicecollection/podcast/MediaDevicePodcastProvider.h index 00e24f6bcb..9a6aeff13c 100644 --- a/src/core-impl/collections/mediadevicecollection/podcast/MediaDevicePodcastProvider.h +++ b/src/core-impl/collections/mediadevicecollection/podcast/MediaDevicePodcastProvider.h @@ -1,61 +1,61 @@ /**************************************************************************************** * Copyright 2010 Bart Cerneels * * * * 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, see . * ****************************************************************************************/ #ifndef MEDIADEVICEPODCASTPROVIDER_H #define MEDIADEVICEPODCASTPROVIDER_H #include "core/podcasts/PodcastProvider.h" #include "core-impl/collections/mediadevicecollection/handler/MediaDeviceHandler.h" namespace Podcasts { class MediaDevicePodcastProvider : public Podcasts::PodcastProvider { public: MediaDevicePodcastProvider( Meta::MediaDeviceHandler *handler ); //TODO:implement these - virtual bool possiblyContainsTrack( const KUrl &url ) const { Q_UNUSED(url); return false;} - virtual Meta::TrackPtr trackForUrl( const KUrl &url ) { Q_UNUSED(url); return Meta::TrackPtr(); } + virtual bool possiblyContainsTrack( const QUrl &url ) const { Q_UNUSED(url); return false;} + virtual Meta::TrackPtr trackForUrl( const QUrl &url ) { Q_UNUSED(url); return Meta::TrackPtr(); } - virtual void addPodcast( const KUrl &url ); + virtual void addPodcast( const QUrl &url ); virtual Podcasts::PodcastChannelPtr addChannel( Podcasts::PodcastChannelPtr channel ); virtual Podcasts::PodcastEpisodePtr addEpisode( Podcasts::PodcastEpisodePtr episode ); virtual Podcasts::PodcastChannelList channels(); virtual void removeSubscription( Podcasts::PodcastChannelPtr channel ); virtual void configureProvider(); virtual void configureChannel( Podcasts::PodcastChannelPtr channel ); // PlaylistProvider methods virtual QString prettyName() const; virtual int category() const { return (int)Playlists::PodcastChannelPlaylist; } virtual Playlists::PlaylistList playlists(); virtual Playlists::PlaylistPtr addPlaylist( Playlists::PlaylistPtr playlist ); virtual Meta::TrackPtr addTrack( Meta::TrackPtr track ); private: Meta::MediaDeviceHandler *m_handler; }; } //namespace Podcasts #endif // MEDIADEVICEPODCASTPROVIDER_H diff --git a/src/core-impl/collections/mtpcollection/MtpCollection.cpp b/src/core-impl/collections/mtpcollection/MtpCollection.cpp index 69f8ad1acd..1aaa7b5a71 100644 --- a/src/core-impl/collections/mtpcollection/MtpCollection.cpp +++ b/src/core-impl/collections/mtpcollection/MtpCollection.cpp @@ -1,86 +1,86 @@ /**************************************************************************************** * Copyright (c) 2008 Alejandro Wainzinger * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "MtpCollection" #include "MtpCollection.h" #include "MtpConnectionAssistant.h" #include "MtpDeviceInfo.h" #include "MediaDeviceInfo.h" #include "amarokconfig.h" #include "core/support/Debug.h" -#include +#include using namespace Collections; AMAROK_EXPORT_COLLECTION( MtpCollectionFactory, mtpcollection ) MtpCollectionFactory::MtpCollectionFactory( QObject *parent, const QVariantList &args ) : MediaDeviceCollectionFactory( parent, args, new MtpConnectionAssistant() ) { m_info = KPluginInfo( "amarok_collection-mtpcollection.desktop", "services" ); } MtpCollectionFactory::~MtpCollectionFactory() { DEBUG_BLOCK // nothing to do } //MtpCollection MtpCollection::MtpCollection( MediaDeviceInfo* info ) : MediaDeviceCollection() { DEBUG_BLOCK /** Fetch Info needed to construct MtpCollection */ debug() << "Getting mtp info"; MtpDeviceInfo *mtpinfo = qobject_cast( info ); debug() << "Getting udi"; m_udi = mtpinfo->udi(); debug() << "constructing handler"; m_handler = new Meta::MtpHandler( this ); //startFullScan();// parse tracks } MtpCollection::~MtpCollection() { DEBUG_BLOCK //if( m_handler ) // qobject_cast ( m_handler )->terminate(); } QString MtpCollection::collectionId() const { return m_udi; } QString MtpCollection::prettyName() const { return m_handler->prettyName(); } diff --git a/src/core-impl/collections/mtpcollection/handler/MtpHandler.cpp b/src/core-impl/collections/mtpcollection/handler/MtpHandler.cpp index a2c07e2ecb..c6260f80b3 100644 --- a/src/core-impl/collections/mtpcollection/handler/MtpHandler.cpp +++ b/src/core-impl/collections/mtpcollection/handler/MtpHandler.cpp @@ -1,1510 +1,1510 @@ /**************************************************************************************** * Copyright (c) 2006 Andy Kelk * * Copyright (c) 2008 Alejandro Wainzinger * * Copyright (c) 2009 Mark Kretschmann * * * * 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) version 3 or * * any later version accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "MtpHandler" #include "MtpHandler.h" #include "MtpCollection.h" #include "core/support/Debug.h" #include "core-impl/meta/file/File.h" // for KIO file handling #include "core/interfaces/Logger.h" #include #include #include "kjob.h" #include -#include +#include #include #include #include #include using namespace Meta; MtpHandler::MtpHandler( Collections::MtpCollection *mc ) : MediaDeviceHandler( mc ) , m_device( 0 ) , m_capacity( 0.0 ) , m_default_parent_folder( 0 ) , m_folders( 0 ) , m_folderStructure() , m_format() , m_name() , m_supportedFiles() , m_isCanceled( false ) , m_wait( false ) , m_dbChanged( false ) , m_trackcounter( 0 ) , m_copyParentId( 0 ) , m_tempDir( new KTempDir() ) { DEBUG_BLOCK m_copyingthreadsafe = true; m_tempDir->setAutoRemove( true ); // init(); } MtpHandler::~MtpHandler() { // TODO: free used memory DEBUG_BLOCK // clear folder structure if ( m_folders != 0 ) { LIBMTP_destroy_folder_t( m_folders ); m_folders = 0; debug() << "Folders destroyed"; } // Delete temporary files //delete m_tempDir; // release device if ( m_device != 0 ) { LIBMTP_Release_Device( m_device ); /* possible race condition with statusbar destructor, will uncomment when fixed */ //Amarok::Components::logger()->longMessage( // i18n( "The MTP device %1 has been disconnected", prettyName() ), StatusBar::Information ); debug() << "Device released"; } } bool MtpHandler::isWritable() const { // TODO: check if read-only return true; } void MtpHandler::init() { mtpFileTypes[LIBMTP_FILETYPE_WAV] = "wav"; mtpFileTypes[LIBMTP_FILETYPE_MP3] = "mp3"; mtpFileTypes[LIBMTP_FILETYPE_WMA] = "wma"; mtpFileTypes[LIBMTP_FILETYPE_OGG] = "ogg"; mtpFileTypes[LIBMTP_FILETYPE_AUDIBLE] = "aa"; mtpFileTypes[LIBMTP_FILETYPE_MP4] = "mp4"; mtpFileTypes[LIBMTP_FILETYPE_UNDEF_AUDIO] = "undef-audio"; mtpFileTypes[LIBMTP_FILETYPE_WMV] = "wmv"; mtpFileTypes[LIBMTP_FILETYPE_AVI] = "avi"; mtpFileTypes[LIBMTP_FILETYPE_MPEG] = "mpg"; mtpFileTypes[LIBMTP_FILETYPE_ASF] = "asf"; mtpFileTypes[LIBMTP_FILETYPE_QT] = "mov"; mtpFileTypes[LIBMTP_FILETYPE_UNDEF_VIDEO] = "undef-video"; mtpFileTypes[LIBMTP_FILETYPE_JPEG] = "jpg"; mtpFileTypes[LIBMTP_FILETYPE_JFIF] = "jfif"; mtpFileTypes[LIBMTP_FILETYPE_TIFF] = "tiff"; mtpFileTypes[LIBMTP_FILETYPE_BMP] = "bmp"; mtpFileTypes[LIBMTP_FILETYPE_GIF] = "gif"; mtpFileTypes[LIBMTP_FILETYPE_PICT] = "pict"; mtpFileTypes[LIBMTP_FILETYPE_PNG] = "png"; mtpFileTypes[LIBMTP_FILETYPE_VCALENDAR1] = "vcs"; mtpFileTypes[LIBMTP_FILETYPE_VCALENDAR2] = "vcs"; mtpFileTypes[LIBMTP_FILETYPE_VCARD2] = "vcf"; mtpFileTypes[LIBMTP_FILETYPE_VCARD3] = "vcf"; mtpFileTypes[LIBMTP_FILETYPE_WINDOWSIMAGEFORMAT] = "wim"; mtpFileTypes[LIBMTP_FILETYPE_WINEXEC] = "exe"; mtpFileTypes[LIBMTP_FILETYPE_TEXT] = "txt"; mtpFileTypes[LIBMTP_FILETYPE_HTML] = "html"; mtpFileTypes[LIBMTP_FILETYPE_AAC] = "aac"; mtpFileTypes[LIBMTP_FILETYPE_FLAC] = "flac"; mtpFileTypes[LIBMTP_FILETYPE_MP2] = "mp3"; mtpFileTypes[LIBMTP_FILETYPE_M4A] = "m4a"; mtpFileTypes[LIBMTP_FILETYPE_DOC] = "doc"; mtpFileTypes[LIBMTP_FILETYPE_XML] = "xml"; mtpFileTypes[LIBMTP_FILETYPE_XLS] = "xls"; mtpFileTypes[LIBMTP_FILETYPE_PPT] = "ppt"; mtpFileTypes[LIBMTP_FILETYPE_MHT] = "mht"; mtpFileTypes[LIBMTP_FILETYPE_JP2] = "jpg"; mtpFileTypes[LIBMTP_FILETYPE_JPX] = "jpx"; mtpFileTypes[LIBMTP_FILETYPE_UNKNOWN] = "unknown"; QString genericError = i18n( "Could not connect to MTP Device" ); m_success = false; // begin checking connected devices LIBMTP_raw_device_t * rawdevices; int numrawdevices; LIBMTP_error_number_t err; debug() << "Initializing MTP stuff"; LIBMTP_Init(); // get list of raw devices debug() << "Getting list of raw devices"; err = LIBMTP_Detect_Raw_Devices( &rawdevices, &numrawdevices ); debug() << "Error is: " << err; switch ( err ) { case LIBMTP_ERROR_NO_DEVICE_ATTACHED: debug() << "No raw devices found."; m_success = false; break; case LIBMTP_ERROR_CONNECTING: debug() << "Detect: There has been an error connecting."; m_success = false; break; case LIBMTP_ERROR_MEMORY_ALLOCATION: debug() << "Detect: Encountered a Memory Allocation Error. Exiting"; m_success = false; break; case LIBMTP_ERROR_NONE: m_success = true; break; default: debug() << "Unhandled mtp error"; m_success = false; break; } if ( m_success ) { debug() << "Got mtp list, connecting to device using thread"; ThreadWeaver::Weaver::instance()->enqueue( new WorkerThread( numrawdevices, rawdevices, this ) ); } else { free( rawdevices ); // emit failed(); } } // this function is threaded bool MtpHandler::iterateRawDevices( int numrawdevices, LIBMTP_raw_device_t* rawdevices ) { DEBUG_BLOCK bool success = false; LIBMTP_mtpdevice_t *device = 0; // test raw device for connectability for ( int i = 0; i < numrawdevices; i++ ) { debug() << "Opening raw device number: " << ( i + 1 ); device = LIBMTP_Open_Raw_Device( &rawdevices[i] ); if ( device == NULL ) { debug() << "Unable to open raw device: " << ( i + 1 ); success = false; continue; } // HACK: not checking serial to confirm the right device is in place // this is not incorrect, and long-term goal is to remove serial number from use altogether /* QString mtpSerial = QString::fromUtf8( LIBMTP_Get_Serialnumber( device ) ); if( !mtpSerial.contains(serial) ) { debug() << "Wrong device, going to next"; debug() << "Expected: " << serial << " but got: " << mtpSerial; success = false; LIBMTP_Release_Device( device ); continue; } */ debug() << "Correct device found"; success = true; break; } m_device = device; if ( m_device == 0 ) { // TODO: error protection success = false; free( rawdevices ); } //QString serial = QString::fromUtf8( LIBMTP_Get_Serialnumber( m_device ) ); // debug() << "Serial is: " << serial; return success; } void MtpHandler::getDeviceInfo() { DEBUG_BLOCK // Get information for device // Get Battery level and print to debug unsigned char max; unsigned char curr; int failed; failed = LIBMTP_Get_Batterylevel( m_device, &max, &curr ); if ( !failed ) debug() << "Battery at: " << curr << "/" << max; else debug() << "Unknown battery level"; if( LIBMTP_Get_Storage( m_device, LIBMTP_STORAGE_SORTBY_NOTSORTED ) != 0 ) { debug() << "Failed to get storage properties, cannot get capacity"; m_capacity = 0.0; } else { m_capacity = m_device->storage->MaxCapacity; } QString modelname = QString( LIBMTP_Get_Modelname( m_device ) ); // NOTE: on next libmtp bump, may reintroduce owner name // for now it doesn't work as expected /* QString ownername = QString( LIBMTP_Get_Friendlyname( m_device ) ); m_name = modelname; if(! ownername.isEmpty() ) if( modelname != ownername ) m_name += " (" + ownername + ')'; else m_name += " (No Owner Name)"; */ m_name = modelname; m_default_parent_folder = m_device->default_music_folder; debug() << "setting default parent : " << m_default_parent_folder; m_folders = LIBMTP_Get_Folder_List( m_device ); uint16_t *filetypes; uint16_t filetypes_len; int ret = LIBMTP_Get_Supported_Filetypes( m_device, &filetypes, &filetypes_len ); if ( ret == 0 ) { uint16_t i; for ( i = 0; i < filetypes_len; ++i ) { debug() << "Device supports: " << mtpFileTypes.value( filetypes[ i ] ); m_supportedFiles << mtpFileTypes.value( filetypes[ i ] ); } } // find supported image types (for album art). if ( m_supportedFiles.indexOf( "jpg" ) ) m_format = "JPEG"; else if ( m_supportedFiles.indexOf( "png" ) ) m_format = "PNG"; else if ( m_supportedFiles.indexOf( "gif" ) ) m_format = "GIF"; free( filetypes ); } void MtpHandler::terminate() { DEBUG_BLOCK // clear folder structure if ( m_folders != 0 ) { LIBMTP_destroy_folder_t( m_folders ); m_folders = 0; } // release device if ( m_device != 0 ) { LIBMTP_Release_Device( m_device ); /* possible race condition with statusbar destructor, will uncomment when fixed Amarok::Components::logger()->longMessage( i18n( "The MTP device %1 has been disconnected", prettyName() ), Amarok::Logger::Information ); */ debug() << "Device released"; } } void MtpHandler::getCopyableUrls( const Meta::TrackList &tracks ) { DEBUG_BLOCK - QMap urls; + QMap urls; QString genericError = i18n( "Could not copy track from device." ); foreach( Meta::TrackPtr trackptr, tracks ) { Meta::MediaDeviceTrackPtr track = Meta::MediaDeviceTrackPtr::dynamicCast( trackptr ); if( !track ) break; QString trackFileName = QString::fromUtf8( m_mtpTrackHash.value( track )->filename ); QString filename = m_tempDir->name() + trackFileName; debug() << "Temp Filename: " << filename; int ret = getTrackToFile( m_mtpTrackHash.value( track )->item_id, filename ); if ( ret != 0 ) { debug() << "Get Track failed: " << ret; /*Amarok::Components::logger()->shortLongMessage( genericError, i18n( "Could not copy track from device." ), StatusBar::Error );*/ } else { urls.insert( trackptr, filename ); } } emit gotCopyableUrls( urls ); } /** * Check (and optionally create) the folder structure to put a * track into. Return the (possibly new) parent folder ID */ uint32_t MtpHandler::checkFolderStructure( const Meta::TrackPtr track, bool create ) { QString artistName; Meta::ArtistPtr artist = track->artist(); if ( !artist || artist->prettyName().isEmpty() ) artistName = i18n( "Unknown Artist" ); else artistName = artist->prettyName(); //FIXME: Port // if( bundle.compilation() == MetaBundle::CompilationYes ) // artist = i18n( "Various Artists" ); QString albumName; Meta::AlbumPtr album = track->album(); if ( !album || album->prettyName().isEmpty() ) albumName = i18n( "Unknown Album" ); else albumName = album->prettyName(); QString genreName; Meta::GenrePtr genre = track->genre(); if ( !genre || genre->prettyName().isEmpty() ) genreName = i18n( "Unknown Genre" ); else genreName = genre->prettyName(); uint32_t parent_id = getDefaultParentId(); QStringList folders = m_folderStructure.split( '/' ); // use slash as a dir separator QString completePath; for ( QStringList::Iterator it = folders.begin(); it != folders.end(); ++it ) { if (( *it ).isEmpty() ) continue; // substitute %a , %b , %g ( *it ).replace( QRegExp( "%a" ), artistName ) .replace( QRegExp( "%b" ), albumName ) .replace( QRegExp( "%g" ), genreName ); // check if it exists uint32_t check_folder = subfolderNameToID(( *it ).toUtf8(), m_folders, parent_id ); // create if not exists (if requested) if ( check_folder == 0 ) { if ( create ) { check_folder = createFolder(( *it ).toUtf8() , parent_id ); if ( check_folder == 0 ) { return 0; } } else { return 0; } } completePath += ( *it ).toUtf8() + '/'; // set new parent parent_id = check_folder; } debug() << "Folder path : " << completePath; return parent_id; } uint32_t MtpHandler::getDefaultParentId( void ) { // Decide which folder to send it to: // If the device gave us a parent_folder setting, we use it uint32_t parent_id = 0; if ( m_default_parent_folder ) { parent_id = m_default_parent_folder; } // Otherwise look for a folder called "Music" else if ( m_folders != 0 ) { parent_id = folderNameToID( qstrdup( QString( "Music" ).toUtf8() ), m_folders ); if ( !parent_id ) { debug() << "Parent folder could not be found. Going to use top level."; } } // Give up and don't set a parent folder, let the device deal with it else { debug() << "No folders found. Going to use top level."; } return parent_id; } /** * Recursively search the folder list for a matching one * and return its ID */ uint32_t MtpHandler::folderNameToID( char *name, LIBMTP_folder_t *folderlist ) { uint32_t i; if ( folderlist == 0 ) return 0; if ( !strcasecmp( name, folderlist->name ) ) return folderlist->folder_id; if (( i = ( folderNameToID( name, folderlist->child ) ) ) ) return i; if (( i = ( folderNameToID( name, folderlist->sibling ) ) ) ) return i; return 0; } /** * Recursively search the folder list for a matching one under the specified * parent ID and return the child's ID */ uint32_t MtpHandler::subfolderNameToID( const char *name, LIBMTP_folder_t *folderlist, uint32_t parent_id ) { uint32_t i; if ( folderlist == 0 ) return 0; if ( !strcasecmp( name, folderlist->name ) && folderlist->parent_id == parent_id ) return folderlist->folder_id; if (( i = ( subfolderNameToID( name, folderlist->child, parent_id ) ) ) ) return i; if (( i = ( subfolderNameToID( name, folderlist->sibling, parent_id ) ) ) ) return i; return 0; } /** * Create a new mtp folder */ uint32_t MtpHandler::createFolder( const char *name, uint32_t parent_id ) { debug() << "Creating new folder '" << name << "' as a child of " << parent_id; char *name_copy = qstrdup( name ); // NOTE: api change, 0 refers to default storage_id uint32_t new_folder_id = LIBMTP_Create_Folder( m_device, name_copy, parent_id, 0 ); delete( name_copy ); debug() << "New folder ID: " << new_folder_id; if ( new_folder_id == 0 ) { debug() << "Attempt to create folder '" << name << "' failed."; return 0; } updateFolders(); return new_folder_id; } /** * Update local cache of mtp folders */ void MtpHandler::updateFolders( void ) { LIBMTP_destroy_folder_t( m_folders ); m_folders = 0; m_folders = LIBMTP_Get_Folder_List( m_device ); } #if 0 void MtpHandler::privateDeleteTrackFromDevice( const Meta::MtpTrackPtr &track ) { DEBUG_BLOCK //If nothing is left in a folder, delete the folder u_int32_t object_id = track->id(); QString genericError = i18n( "Could not delete item" ); debug() << "delete this id : " << object_id; int status = LIBMTP_Delete_Object( m_device, object_id ); if ( status != 0 ) { debug() << "delete object failed"; Amarok::Components::logger()->longMessage( i18n( "Delete failed" ), Amarok::Logger::Error ); // return false; } debug() << "object deleted"; // return true; m_titlemap.remove( track->name(), Meta::TrackPtr::staticCast( track ) ); } #endif int MtpHandler::getTrackToFile( const uint32_t id, const QString & filename ) { return LIBMTP_Get_Track_To_File( m_device, id, filename.toUtf8(), 0, 0 ); } int MtpHandler::progressCallback( uint64_t const sent, uint64_t const total, void const * const data ) { DEBUG_BLOCK Q_UNUSED( sent ); MtpHandler *handler = ( MtpHandler* )( data ); // NOTE: setting max many times wastes cycles, // but how else to get total outside of callback? debug() << "Setting max to: " << (( int ) total ); debug() << "Device: " << handler->prettyName(); /* handler->setBarMaximum(( int ) total ); handler->setBarProgress(( int ) sent ); if ( sent == total ) handler->endBarProgressOperation(); */ return 0; } QString MtpHandler::prettyName() const { return m_name; } /// Begin MediaDeviceHandler overrides void MtpHandler::findPathToCopy( const Meta::TrackPtr &srcTrack, const Meta::MediaDeviceTrackPtr &destTrack ) { Q_UNUSED( destTrack ); uint32_t parent_id = 0; if ( !m_folderStructure.isEmpty() ) { parent_id = checkFolderStructure( srcTrack, true ); // true means create if ( parent_id == 0 ) { debug() << "Could not create new parent (" << m_folderStructure << ")"; /*Amarok::Components::logger()->shortLongMessage( genericError, i18n( "Cannot create parent folder. Check your structure." ), Amarok::Logger::Error );*/ return; } } else { parent_id = getDefaultParentId(); } debug() << "Parent id : " << parent_id; m_copyParentId = parent_id; } bool MtpHandler::libCopyTrack( const Meta::TrackPtr &srcTrack, Meta::MediaDeviceTrackPtr &destTrack ) { DEBUG_BLOCK findPathToCopy( srcTrack, destTrack ); debug() << "sending..."; debug() << "Playable Url is: " << srcTrack->playableUrl(); debug() << "Sending file with path: " << srcTrack->playableUrl().path().toUtf8(); int ret = LIBMTP_Send_Track_From_File( m_device, qstrdup( srcTrack->playableUrl().path().toUtf8() ), m_mtpTrackHash.value( destTrack ), 0, 0 ); debug() << "sent"; // emit canCopyMoreTracks(); // emit libCopyTrackDone( srcTrack ); return ( ret == 0 ); } bool MtpHandler::libDeleteTrackFile( const Meta::MediaDeviceTrackPtr &track ) { slotFinalizeTrackRemove( Meta::TrackPtr::staticCast( track ) ); return true; } void MtpHandler::libDeleteTrack( const Meta::MediaDeviceTrackPtr &track ) { DEBUG_BLOCK LIBMTP_track_struct *mtptrack = m_mtpTrackHash.value( track ); m_mtpTrackHash.remove( track ); quint32 object_id = mtptrack->item_id; const QString genericError = i18n( "Could not delete item" ); int status = LIBMTP_Delete_Object( m_device, object_id ); removeNextTrackFromDevice(); if( status != 0 ) debug() << "delete object failed"; else debug() << "object deleted"; } void MtpHandler::setDatabaseChanged() { m_dbChanged = true; } void MtpHandler::prepareToParseTracks() { DEBUG_BLOCK m_currentTrackList = LIBMTP_Get_Tracklisting_With_Callback( m_device, 0, this ); } bool MtpHandler::isEndOfParseTracksList() { return m_currentTrackList ? false : true; } void MtpHandler::prepareToParseNextTrack() { m_currentTrackList = m_currentTrackList->next; } void MtpHandler::nextTrackToParse() { m_currentTrack = m_currentTrackList; } /// Playlist Parsing void MtpHandler::prepareToParsePlaylists() { m_currentPlaylistList = LIBMTP_Get_Playlist_List( m_device ); } bool MtpHandler::isEndOfParsePlaylistsList() { return (m_currentPlaylistList == 0); } void MtpHandler::prepareToParseNextPlaylist() { m_currentPlaylistList = m_currentPlaylistList->next; } void MtpHandler::nextPlaylistToParse() { m_currentPlaylist = m_currentPlaylistList; } bool MtpHandler::shouldNotParseNextPlaylist() { // NOTE: parse all return false; } void MtpHandler::prepareToParsePlaylistTracks() { m_trackcounter = 0; } bool MtpHandler::isEndOfParsePlaylist() { return (m_trackcounter >= m_currentPlaylist->no_tracks); } void MtpHandler::prepareToParseNextPlaylistTrack() { m_trackcounter++; } void MtpHandler::nextPlaylistTrackToParse() { m_currentTrack = m_idTrackHash.value( m_currentPlaylist->tracks[ m_trackcounter ] ); } Meta::MediaDeviceTrackPtr MtpHandler::libGetTrackPtrForTrackStruct() { return m_mtpTrackHash.key( m_currentTrack ); } QString MtpHandler::libGetPlaylistName() { return QString::fromUtf8( m_currentPlaylist->name ); } void MtpHandler::setAssociatePlaylist( const Playlists::MediaDevicePlaylistPtr &playlist ) { m_mtpPlaylisthash[ playlist ] = m_currentPlaylist; } void MtpHandler::libSavePlaylist( const Playlists::MediaDevicePlaylistPtr &playlist, const QString& name ) { DEBUG_BLOCK Meta::TrackList tracklist = const_cast ( playlist )->tracks(); // Make new playlist LIBMTP_playlist_t *metadata = LIBMTP_new_playlist_t(); metadata->name = qstrdup( name.toUtf8() ); const int trackCount = tracklist.count(); if( trackCount > 0 ) { uint32_t *tracks = ( uint32_t* )malloc( sizeof( uint32_t ) * trackCount ); uint32_t i = 0; foreach( Meta::TrackPtr trk, tracklist ) { if( !trk ) // playlists might contain invalid tracks. see BUG: 297816 continue; Meta::MediaDeviceTrackPtr track = Meta::MediaDeviceTrackPtr::staticCast( trk ); tracks[i] = m_mtpTrackHash.value( track )->item_id; } metadata->tracks = tracks; metadata->no_tracks = trackCount; } else { debug() << "no tracks available for playlist " << metadata->name; metadata->no_tracks = 0; } QString genericError = i18n( "Could not save playlist." ); debug() << "creating new playlist : " << metadata->name << endl; int ret = LIBMTP_Create_New_Playlist( m_device, metadata ); if( ret == 0 ) { m_mtpPlaylisthash[ playlist ] = metadata; debug() << "playlist saved : " << metadata->playlist_id << endl; } else debug () << "Could not create new playlist on device."; } void MtpHandler::deletePlaylist( const Playlists::MediaDevicePlaylistPtr &playlist ) { DEBUG_BLOCK LIBMTP_playlist_t *pl = m_mtpPlaylisthash.value( playlist ); if( pl ) { m_mtpPlaylisthash.remove( playlist ); quint32 object_id = pl->playlist_id; QString genericError = i18n( "Could not delete item" ); debug() << "delete this id : " << object_id; int status = LIBMTP_Delete_Object( m_device, object_id ); if ( status != 0 ) { debug() << "delete object failed"; } else debug() << "object deleted"; } } void MtpHandler::renamePlaylist( const Playlists::MediaDevicePlaylistPtr &playlist ) { DEBUG_BLOCK LIBMTP_playlist_t *pl = m_mtpPlaylisthash.value( playlist ); if( pl ) { debug() << "updating playlist : " << pl->name << endl; int ret = LIBMTP_Update_Playlist( m_device, pl ); if( ret != 0 ) { debug() << "Could not rename playlist"; } else debug() << "Playlist renamed"; } } void MtpHandler::setAssociateTrack( const Meta::MediaDeviceTrackPtr track ) { m_mtpTrackHash[ track ] = m_currentTrack; m_idTrackHash[ m_currentTrack->item_id ] = m_currentTrack; } QStringList MtpHandler::supportedFormats() { return m_supportedFiles; } QString MtpHandler::libGetTitle( const Meta::MediaDeviceTrackPtr &track ) { return QString::fromUtf8( m_mtpTrackHash.value( track )->title ); } QString MtpHandler::libGetAlbum( const Meta::MediaDeviceTrackPtr &track ) { return QString::fromUtf8( m_mtpTrackHash.value( track )->album ); } QString MtpHandler::libGetArtist( const Meta::MediaDeviceTrackPtr &track ) { return QString::fromUtf8( m_mtpTrackHash.value( track )->artist ); } QString MtpHandler::libGetAlbumArtist( const Meta::MediaDeviceTrackPtr &track ) { //Album artist isn't supported by libmtp ATM. Q_UNUSED( track ) return QString(); } QString MtpHandler::libGetComposer( const Meta::MediaDeviceTrackPtr &track ) { return QString::fromUtf8( m_mtpTrackHash.value( track )->composer ); } QString MtpHandler::libGetGenre( const Meta::MediaDeviceTrackPtr &track ) { return QString::fromUtf8( m_mtpTrackHash.value( track )->genre ); } int MtpHandler::libGetYear( const Meta::MediaDeviceTrackPtr &track ) { return QString::fromUtf8( m_mtpTrackHash.value( track )->date ).mid( 0, 4 ).toUInt(); } qint64 MtpHandler::libGetLength( const Meta::MediaDeviceTrackPtr &track ) { if ( m_mtpTrackHash.value( track )->duration > 0 ) return ( ( m_mtpTrackHash.value( track )->duration ) ); return 0; } int MtpHandler::libGetTrackNumber( const Meta::MediaDeviceTrackPtr &track ) { return m_mtpTrackHash.value( track )->tracknumber; } QString MtpHandler::libGetComment( const Meta::MediaDeviceTrackPtr &track ) { // NOTE: defaulting, since not provided Q_UNUSED( track ); return QString(); } int MtpHandler::libGetDiscNumber( const Meta::MediaDeviceTrackPtr &track ) { Q_UNUSED( track ); // NOTE: defaulting, since not provided return 1; } int MtpHandler::libGetBitrate( const Meta::MediaDeviceTrackPtr &track ) { return m_mtpTrackHash.value( track )->bitrate; } int MtpHandler::libGetSamplerate( const Meta::MediaDeviceTrackPtr &track ) { return m_mtpTrackHash.value( track )->samplerate; } qreal MtpHandler::libGetBpm( const Meta::MediaDeviceTrackPtr &track ) { Q_UNUSED( track ); // NOTE: defaulting, since not provided return 0.0; } int MtpHandler::libGetFileSize( const Meta::MediaDeviceTrackPtr &track ) { return m_mtpTrackHash.value( track )->filesize; } int MtpHandler::libGetPlayCount( const Meta::MediaDeviceTrackPtr &track ) { return m_mtpTrackHash.value( track )->usecount; } QDateTime MtpHandler::libGetLastPlayed( const Meta::MediaDeviceTrackPtr &track ) { Q_UNUSED( track ); // NOTE: defaulting, since not provided return QDateTime(); } // TODO: implement rating int MtpHandler::libGetRating( const Meta::MediaDeviceTrackPtr &track ) { return ( m_mtpTrackHash.value( track )->rating / 10 ); } QString MtpHandler::libGetType( const Meta::MediaDeviceTrackPtr &track ) { return mtpFileTypes.value( m_mtpTrackHash.value( track )->filetype ); } -KUrl +QUrl MtpHandler::libGetPlayableUrl( const Meta::MediaDeviceTrackPtr &track ) { Q_UNUSED( track ) // NOTE: not a real url, using for unique key for qm - return KUrl( QString::number( m_mtpTrackHash.value( track )->item_id, 10 ) ); + return QUrl( QString::number( m_mtpTrackHash.value( track )->item_id, 10 ) ); } float MtpHandler::totalCapacity() const { DEBUG_BLOCK return m_capacity; } float MtpHandler::usedCapacity() const { DEBUG_BLOCK if( LIBMTP_Get_Storage( m_device, LIBMTP_STORAGE_SORTBY_NOTSORTED ) != 0 ) { debug() << "Failed to get storage properties, cannot get capacity"; return 0.0; } return ( totalCapacity() - m_device->storage->FreeSpaceInBytes ); } /// Sets void MtpHandler::libSetTitle( Meta::MediaDeviceTrackPtr& track, const QString& title ) { m_mtpTrackHash.value( track )->title = ( title.isEmpty() ? qstrdup( "" ) : qstrdup( title.toUtf8() ) ); debug() << "Set to: " << m_mtpTrackHash.value( track )->title; } void MtpHandler::libSetAlbum( Meta::MediaDeviceTrackPtr &track, const QString& album ) { m_mtpTrackHash.value( track )->album = ( album.isEmpty() ? qstrdup( "" ) : qstrdup( album.toUtf8() ) ); debug() << "Set to: " << m_mtpTrackHash.value( track )->album; } void MtpHandler::libSetArtist( Meta::MediaDeviceTrackPtr &track, const QString& artist ) { m_mtpTrackHash.value( track )->artist = ( artist.isEmpty() ? qstrdup( "" ) : qstrdup( artist.toUtf8() ) ); debug() << "Set to: " << m_mtpTrackHash.value( track )->artist; } void MtpHandler::libSetAlbumArtist( MediaDeviceTrackPtr &track, const QString &albumArtist ) { //Album artist isn't supported by libmtp ATM. Q_UNUSED( track ) Q_UNUSED( albumArtist ) } void MtpHandler::libSetComposer( Meta::MediaDeviceTrackPtr &track, const QString& composer ) { m_mtpTrackHash.value( track )->composer = ( composer.isEmpty() ? qstrdup( "" ) : qstrdup( composer.toUtf8() ) ); debug() << "Set to: " << m_mtpTrackHash.value( track )->composer; } void MtpHandler::libSetGenre( Meta::MediaDeviceTrackPtr &track, const QString& genre ) { m_mtpTrackHash.value( track )->genre = ( genre.isEmpty() ? qstrdup( "" ) : qstrdup( genre.toUtf8() ) ); debug() << "Set to: " << m_mtpTrackHash.value( track )->genre; } void MtpHandler::libSetYear( Meta::MediaDeviceTrackPtr &track, const QString& year ) { if( year.toInt() > 0 ) { QString date; QTextStream( &date ) << year.toInt() << "0101T0000.0"; m_mtpTrackHash.value( track )->date = qstrdup( date.toUtf8() ); } else m_mtpTrackHash.value( track )->date = qstrdup( "00010101T0000.0" ); } void MtpHandler::libSetLength( Meta::MediaDeviceTrackPtr &track, int length ) { m_mtpTrackHash.value( track )->duration = ( length > 0 ? length : 0 ); } void MtpHandler::libSetTrackNumber( Meta::MediaDeviceTrackPtr &track, int tracknum ) { m_mtpTrackHash.value( track )->tracknumber = tracknum; } void MtpHandler::libSetComment( Meta::MediaDeviceTrackPtr &track, const QString& comment ) { // NOTE: defaulting, since not provided Q_UNUSED( track ) Q_UNUSED( comment ) } void MtpHandler::libSetDiscNumber( Meta::MediaDeviceTrackPtr &track, int discnum ) { // NOTE: defaulting, since not provided Q_UNUSED( track ) Q_UNUSED( discnum ) } void MtpHandler::libSetBitrate( Meta::MediaDeviceTrackPtr &track, int bitrate ) { m_mtpTrackHash.value( track )->bitrate = bitrate; } void MtpHandler::libSetSamplerate( Meta::MediaDeviceTrackPtr &track, int samplerate ) { m_mtpTrackHash.value( track )->samplerate = samplerate; } void MtpHandler::libSetBpm( Meta::MediaDeviceTrackPtr &track, qreal bpm ) { // NOTE: defaulting, since not provided Q_UNUSED( track ) Q_UNUSED( bpm ) } void MtpHandler::libSetFileSize( Meta::MediaDeviceTrackPtr &track, int filesize ) { m_mtpTrackHash.value( track )->filesize = filesize; } void MtpHandler::libSetPlayCount( Meta::MediaDeviceTrackPtr &track, int playcount ) { m_mtpTrackHash.value( track )->usecount = playcount; } void MtpHandler::libSetLastPlayed( Meta::MediaDeviceTrackPtr &track, const QDateTime &lastplayed) { Q_UNUSED( track ) Q_UNUSED( lastplayed ) } void MtpHandler::libSetRating( Meta::MediaDeviceTrackPtr &track, int rating ) { m_mtpTrackHash.value( track )->rating = ( rating * 10 ); } void MtpHandler::libSetType( Meta::MediaDeviceTrackPtr &track, const QString& type ) { debug() << "filetype : " << type; if ( type == "mp3" ) { m_mtpTrackHash.value( track )->filetype = LIBMTP_FILETYPE_MP3; } else if ( type == "ogg" ) { m_mtpTrackHash.value( track )->filetype = LIBMTP_FILETYPE_OGG; } else if ( type == "wma" ) { m_mtpTrackHash.value( track )->filetype = LIBMTP_FILETYPE_WMA; } else if ( type == "mp4" ) { m_mtpTrackHash.value( track )->filetype = LIBMTP_FILETYPE_MP4; } else { // Couldn't recognise an Amarok filetype. // fallback to checking the extension (e.g. .wma, .ogg, etc) debug() << "No filetype found by Amarok filetype"; const QString extension = type.toLower(); int libmtp_type = m_supportedFiles.indexOf( extension ); if ( libmtp_type >= 0 ) { int keyIndex = mtpFileTypes.values().indexOf( extension ); libmtp_type = mtpFileTypes.keys()[keyIndex]; m_mtpTrackHash.value( track )->filetype = ( LIBMTP_filetype_t ) libmtp_type; debug() << "set filetype to " << libmtp_type << " based on extension of ." << extension; } else { debug() << "We do not support the extension ." << extension; /* Amarok::Components::logger()->shortLongMessage( genericError, i18n( "Cannot determine a valid file type" ), Amarok::Logger::Error );*/ } } debug() << "Filetype set to: " << mtpFileTypes.value( m_mtpTrackHash.value( track )->filetype ); } void MtpHandler::libSetPlayableUrl( Meta::MediaDeviceTrackPtr &destTrack, const Meta::TrackPtr &srcTrack ) { if( !srcTrack->playableUrl().fileName().isEmpty() ) m_mtpTrackHash.value( destTrack )->filename = qstrdup( srcTrack->playableUrl().fileName().toUtf8() ); } void MtpHandler::libCreateTrack( const Meta::MediaDeviceTrackPtr& track ) { m_mtpTrackHash[ track ] = LIBMTP_new_track_t(); m_mtpTrackHash.value( track )->item_id = 0; m_mtpTrackHash.value( track )->parent_id = m_copyParentId; m_mtpTrackHash.value( track )->storage_id = 0; // default storage id } void MtpHandler::prepareToPlay( Meta::MediaDeviceTrackPtr &track ) { DEBUG_BLOCK - KUrl url; + QUrl url; if( m_cachedTracks.contains( track ) ) { debug() << "File is already copied, simply return"; - //m_playableUrl = KUrl::fromPath( m_playableUrl ); + //m_playableUrl = QUrl::fromLocalFile( m_playableUrl ); } else { QString tempPath = setTempFile( track, libGetType( track ) ); track->setPlayableUrl( tempPath ); debug() << "Beginning temporary file copy"; // m_tempfile.open(); bool success = !(getTrackToFile( m_mtpTrackHash.value( track )->item_id , track->playableUrl().path() ) ); debug() << "File transfer complete"; if( success ) { debug() << "File transfer successful!"; - //m_playableUrl = KUrl::fromPath( m_playableUrl ); + //m_playableUrl = QUrl::fromLocalFile( m_playableUrl ); } else { debug() << "File transfer failed!"; - //m_playableUrl = KUrl::fromPath( "" ); + //m_playableUrl = QUrl::fromLocalFile( "" ); m_cachedTracks.remove( track ); } } } QString MtpHandler::setTempFile( Meta::MediaDeviceTrackPtr &track, const QString &format ) { m_cachedTracks[ track ] = new KTemporaryFile(); m_cachedTracks.value( track )->setSuffix( ('.' + format) ); // set suffix based on info from libmtp if (!m_cachedTracks.value( track )->open()) return QString(); QFileInfo tempFileInfo( *(m_cachedTracks.value( track ) ) ); // get info for path QString tempPath = tempFileInfo.absoluteFilePath(); // path m_cachedTracks.value( track )->setAutoRemove( true ); return tempPath; } void MtpHandler::slotDeviceMatchSucceeded( ThreadWeaver::Job* job ) { DEBUG_BLOCK if( !m_memColl ) // try to fix BUG:279966 return; if ( job->success() ) { getDeviceInfo(); // debug() << "Device matches serial, emitting succeeded()"; m_memColl->slotAttemptConnectionDone( true ); } else m_memColl->slotAttemptConnectionDone( false ); } void MtpHandler::slotDeviceMatchFailed( ThreadWeaver::Job* job ) { DEBUG_BLOCK if( !m_memColl ) // try to fix BUG:279966 return; debug() << "Running slot device match failed"; disconnect( job, SIGNAL(done(ThreadWeaver::Job*)), this, SLOT(slotDeviceMatchSucceeded()) ); m_memColl->slotAttemptConnectionDone( false ); } void MtpHandler::updateTrack( Meta::MediaDeviceTrackPtr &track ) { DEBUG_BLOCK // pull out track struct to prepare for update LIBMTP_track_t *mtptrack = m_mtpTrackHash.value( track ); // commence update on device int failed = LIBMTP_Update_Track_Metadata( m_device, mtptrack ); if ( !failed ) debug() << "Metadata update succeeded!"; else debug() << "Failed to update metadata"; } /// Capability-related functions bool MtpHandler::hasCapabilityInterface( Handler::Capability::Type type ) const { switch( type ) { case Handler::Capability::Readable: return true; case Handler::Capability::Playlist: return true; case Handler::Capability::Writable: return true; default: return false; } } Handler::Capability* MtpHandler::createCapabilityInterface( Handler::Capability::Type type ) { switch( type ) { case Handler::Capability::Readable: return new Handler::MtpReadCapability( this ); case Handler::Capability::Playlist: return new Handler::MtpPlaylistCapability( this ); case Handler::Capability::Writable: return new Handler::MtpWriteCapability( this ); default: return 0; } } WorkerThread::WorkerThread( int numrawdevices, LIBMTP_raw_device_t* rawdevices, MtpHandler* handler ) : ThreadWeaver::Job() , m_success( false ) , m_numrawdevices( numrawdevices ) , m_rawdevices( rawdevices ) , m_handler( handler ) { connect( this, SIGNAL(failed(ThreadWeaver::Job*)), m_handler, SLOT(slotDeviceMatchFailed(ThreadWeaver::Job*)) ); connect( this, SIGNAL(done(ThreadWeaver::Job*)), m_handler, SLOT(slotDeviceMatchSucceeded(ThreadWeaver::Job*)) ); connect( this, SIGNAL(done(ThreadWeaver::Job*)), this, SLOT(deleteLater()) ); } WorkerThread::~WorkerThread() { //nothing to do } bool WorkerThread::success() const { return m_success; } void WorkerThread::run() { m_success = m_handler->iterateRawDevices( m_numrawdevices, m_rawdevices ); } /* void MtpHandler::slotCopyNextTrackFailed( ThreadWeaver::Job* job ) { Q_UNUSED( job ); m_copyFailed = true; QString error = "Job Failed"; m_tracksFailed.insert( m_lastTrackCopied, error ); copyNextTrackToDevice(); } void MtpHandler::slotCopyNextTrackToDevice( ThreadWeaver::Job* job ) { if ( job->success() ) { emit incrementProgress(); } else { m_copyFailed = true; QString error = "MTP copy error"; m_tracksFailed.insert( m_lastTrackCopied, error ); } copyNextTrackToDevice(); } CopyWorkerThread::CopyWorkerThread( const Meta::TrackPtr &track, MtpHandler* handler ) : ThreadWeaver::Job() , m_success( false ) , m_track( track ) , m_handler( handler ) { connect( this, SIGNAL(failed(ThreadWeaver::Job*)), m_handler, SLOT(slotCopyNextTrackFailed(ThreadWeaver::Job*)) ); connect( this, SIGNAL(done(ThreadWeaver::Job*)), m_handler, SLOT(slotCopyNextTrackToDevice(ThreadWeaver::Job*)) ); connect( this, SIGNAL(done(ThreadWeaver::Job*)), this, SLOT(deleteLater()) ); } CopyWorkerThread::~CopyWorkerThread() { //nothing to do } bool CopyWorkerThread::success() const { return m_success; } void CopyWorkerThread::run() { m_success = m_handler->privateCopyTrackToDevice( m_track ); } */ diff --git a/src/core-impl/collections/mtpcollection/handler/MtpHandler.h b/src/core-impl/collections/mtpcollection/handler/MtpHandler.h index b34083b5fb..4f12c4f2c5 100644 --- a/src/core-impl/collections/mtpcollection/handler/MtpHandler.h +++ b/src/core-impl/collections/mtpcollection/handler/MtpHandler.h @@ -1,276 +1,276 @@ /**************************************************************************************** * Copyright (c) 2006 Andy Kelk * * Copyright (c) 2008 Alejandro Wainzinger * * Copyright (c) 2009 Mark Kretschmann * * * * 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) version 3 or * * any later version accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * 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, see . * ****************************************************************************************/ #ifndef MTPHANDLER_H #define MTPHANDLER_H #include #include "MtpPlaylistCapability.h" #include "MtpReadCapability.h" #include "MtpWriteCapability.h" #include "MediaDeviceMeta.h" #include "MediaDeviceHandler.h" #include #include #include #include #include #include #include #include #include class QString; class QMutex; class QStringList; namespace Collections { class MtpCollection; } namespace Meta { typedef QMultiMap TitleMap; class WorkerThread; /* The libmtp backend for all Mtp calls */ class MtpHandler : public MediaDeviceHandler { Q_OBJECT public: MtpHandler( Collections::MtpCollection *mc ); virtual ~MtpHandler(); friend class WorkerThread; virtual void init(); // collection virtual bool isWritable() const; virtual void getCopyableUrls( const Meta::TrackList &tracks ); virtual QString prettyName() const; virtual void prepareToPlay( Meta::MediaDeviceTrackPtr &track ); /// Capability-related methods virtual bool hasCapabilityInterface( Handler::Capability::Type type ) const; virtual Handler::Capability* createCapabilityInterface( Handler::Capability::Type type ); friend class Handler::MtpPlaylistCapability; friend class Handler::MtpReadCapability; friend class Handler::MtpWriteCapability; protected: /* Parsing of Tracks on Device */ virtual void prepareToParseTracks(); virtual bool isEndOfParseTracksList(); virtual void prepareToParseNextTrack(); virtual void nextTrackToParse(); virtual void setAssociateTrack( const Meta::MediaDeviceTrackPtr track ); virtual void prepareToParsePlaylists(); virtual bool isEndOfParsePlaylistsList(); virtual void prepareToParseNextPlaylist(); virtual void nextPlaylistToParse(); virtual bool shouldNotParseNextPlaylist(); virtual void prepareToParsePlaylistTracks(); virtual bool isEndOfParsePlaylist(); virtual void prepareToParseNextPlaylistTrack(); virtual void nextPlaylistTrackToParse(); virtual QStringList supportedFormats(); virtual void findPathToCopy( const Meta::TrackPtr &srcTrack, const Meta::MediaDeviceTrackPtr &destTrack ); virtual bool libCopyTrack( const Meta::TrackPtr &srcTrack, Meta::MediaDeviceTrackPtr &destTrack ); virtual bool libDeleteTrackFile( const Meta::MediaDeviceTrackPtr &track ); virtual void libCreateTrack( const Meta::MediaDeviceTrackPtr &track ); virtual void libDeleteTrack( const Meta::MediaDeviceTrackPtr &track ); virtual Meta::MediaDeviceTrackPtr libGetTrackPtrForTrackStruct(); virtual QString libGetPlaylistName(); virtual void setAssociatePlaylist( const Playlists::MediaDevicePlaylistPtr &playlist ); virtual void libSavePlaylist( const Playlists::MediaDevicePlaylistPtr &playlist, const QString& name ); virtual void deletePlaylist( const Playlists::MediaDevicePlaylistPtr &playlist ); virtual void renamePlaylist( const Playlists::MediaDevicePlaylistPtr &playlist ); virtual void addTrackInDB( const Meta::MediaDeviceTrackPtr &track ) { Q_UNUSED( track ) } virtual void removeTrackFromDB( const Meta::MediaDeviceTrackPtr &track ) { Q_UNUSED( track ) } virtual void setDatabaseChanged(); virtual QString libGetTitle( const Meta::MediaDeviceTrackPtr &track ); virtual QString libGetAlbum( const Meta::MediaDeviceTrackPtr &track ); virtual QString libGetArtist( const Meta::MediaDeviceTrackPtr &track ); virtual QString libGetAlbumArtist( const Meta::MediaDeviceTrackPtr &track ); virtual QString libGetComposer( const Meta::MediaDeviceTrackPtr &track ); virtual QString libGetGenre( const Meta::MediaDeviceTrackPtr &track ); virtual int libGetYear( const Meta::MediaDeviceTrackPtr &track ); virtual qint64 libGetLength( const Meta::MediaDeviceTrackPtr &track ); virtual int libGetTrackNumber( const Meta::MediaDeviceTrackPtr &track ); virtual QString libGetComment( const Meta::MediaDeviceTrackPtr &track ); virtual int libGetDiscNumber( const Meta::MediaDeviceTrackPtr &track ); virtual int libGetBitrate( const Meta::MediaDeviceTrackPtr &track ); virtual int libGetSamplerate( const Meta::MediaDeviceTrackPtr &track ); virtual qreal libGetBpm( const Meta::MediaDeviceTrackPtr &track ); virtual int libGetFileSize( const Meta::MediaDeviceTrackPtr &track ); virtual int libGetPlayCount( const Meta::MediaDeviceTrackPtr &track ); virtual QDateTime libGetLastPlayed( const Meta::MediaDeviceTrackPtr &track ); virtual int libGetRating( const Meta::MediaDeviceTrackPtr &track ) ; virtual QString libGetType( const Meta::MediaDeviceTrackPtr &track ); - virtual KUrl libGetPlayableUrl( const Meta::MediaDeviceTrackPtr &track ); + virtual QUrl libGetPlayableUrl( const Meta::MediaDeviceTrackPtr &track ); virtual float usedCapacity() const; virtual float totalCapacity() const; virtual void libSetTitle( Meta::MediaDeviceTrackPtr &track, const QString& title ); virtual void libSetAlbum( Meta::MediaDeviceTrackPtr &track, const QString& album ); virtual void libSetArtist( Meta::MediaDeviceTrackPtr &track, const QString& artist ); virtual void libSetAlbumArtist( Meta::MediaDeviceTrackPtr &track, const QString& albumArtist ); virtual void libSetComposer( Meta::MediaDeviceTrackPtr &track, const QString& composer ); virtual void libSetGenre( Meta::MediaDeviceTrackPtr &track, const QString& genre ); virtual void libSetYear( Meta::MediaDeviceTrackPtr &track, const QString& year ); virtual void libSetLength( Meta::MediaDeviceTrackPtr &track, int length ); virtual void libSetTrackNumber( Meta::MediaDeviceTrackPtr &track, int tracknum ); virtual void libSetComment( Meta::MediaDeviceTrackPtr &track, const QString& comment ); virtual void libSetDiscNumber( Meta::MediaDeviceTrackPtr &track, int discnum ); virtual void libSetBitrate( Meta::MediaDeviceTrackPtr &track, int bitrate ); virtual void libSetSamplerate( Meta::MediaDeviceTrackPtr &track, int samplerate ); virtual void libSetBpm( Meta::MediaDeviceTrackPtr &track, qreal bpm ); virtual void libSetFileSize( Meta::MediaDeviceTrackPtr &track, int filesize ); virtual void libSetPlayCount( Meta::MediaDeviceTrackPtr &track, int playcount ); virtual void libSetLastPlayed( Meta::MediaDeviceTrackPtr &track, const QDateTime &lastplayed ); virtual void libSetRating( Meta::MediaDeviceTrackPtr &track, int rating ) ; virtual void libSetType( Meta::MediaDeviceTrackPtr &track, const QString& type ); virtual void libSetPlayableUrl( Meta::MediaDeviceTrackPtr &destTrack, const Meta::TrackPtr &srcTrack ); virtual void prepareToCopy() {} virtual void prepareToDelete() {} /// libmtp-specific private slots: void slotDeviceMatchSucceeded( ThreadWeaver::Job* job ); void slotDeviceMatchFailed( ThreadWeaver::Job* job ); private: bool iterateRawDevices( int numrawdevices, LIBMTP_raw_device_t* rawdevices ); void getDeviceInfo(); void terminate(); int getTrackToFile( const uint32_t id, const QString & filename ); // Some internal stuff that must be public due to libmtp being in C static int progressCallback( uint64_t const sent, uint64_t const total, void const * const data ); // file-copying related functions uint32_t checkFolderStructure( const Meta::TrackPtr track, bool create ); uint32_t getDefaultParentId( void ); uint32_t folderNameToID( char *name, LIBMTP_folder_t *folderlist ); uint32_t subfolderNameToID( const char *name, LIBMTP_folder_t *folderlist, uint32_t parent_id ); uint32_t createFolder( const char *name, uint32_t parent_id ); void updateFolders( void ); QString setTempFile( Meta::MediaDeviceTrackPtr &track, const QString &format ); virtual void updateTrack( Meta::MediaDeviceTrackPtr &track ); // mtp database LIBMTP_mtpdevice_t *m_device; float m_capacity; QMap mtpFileTypes; uint32_t m_default_parent_folder; LIBMTP_folder_t *m_folders; QString m_folderStructure; QString m_format; QString m_name; QStringList m_supportedFiles; QMutex m_critical_mutex; // KIO-related Vars (to be moved elsewhere eventually) bool m_isCanceled; bool m_wait; bool m_dbChanged; LIBMTP_track_t* m_currentTrackList; LIBMTP_track_t* m_currentTrack; LIBMTP_playlist_t* m_currentPlaylistList; LIBMTP_playlist_t* m_currentPlaylist; QHash m_mtpPlaylisthash; uint32_t m_trackcounter; // Hash that associates an LIBMTP_track_t* to every Track* QHash m_mtpTrackHash; // Keeps track of which tracks have been copied/cached for playing QHash m_cachedTracks; // Maps id's to tracks QHash m_idTrackHash; // parentid calculated for new track copied to device uint32_t m_copyParentId; // Used as temporary location for copying files from mtp KTempDir *m_tempDir; }; class WorkerThread : public ThreadWeaver::Job { Q_OBJECT public: WorkerThread( int numrawdevices, LIBMTP_raw_device_t* rawdevices, MtpHandler* handler ); virtual ~WorkerThread(); virtual bool success() const; protected: virtual void run(); private: bool m_success; int m_numrawdevices; LIBMTP_raw_device_t* m_rawdevices; MtpHandler *m_handler; }; } #endif diff --git a/src/core-impl/collections/mtpcollection/handler/capabilities/MtpReadCapability.cpp b/src/core-impl/collections/mtpcollection/handler/capabilities/MtpReadCapability.cpp index 8ec2250bfa..1b3043fe20 100644 --- a/src/core-impl/collections/mtpcollection/handler/capabilities/MtpReadCapability.cpp +++ b/src/core-impl/collections/mtpcollection/handler/capabilities/MtpReadCapability.cpp @@ -1,194 +1,194 @@ /**************************************************************************************** * Copyright (c) 2009 Alejandro Wainzinger * * Copyright (c) 2009 Mark Kretschmann * * * * 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, see . * ****************************************************************************************/ #include "MtpReadCapability.h" #include "MtpHandler.h" using namespace Handler; MtpReadCapability::MtpReadCapability( Meta::MtpHandler *handler ) : Handler::ReadCapability( handler ) , m_handler( handler ) {} void MtpReadCapability::prepareToParseTracks() { if( m_handler ) m_handler.data()->prepareToParseTracks(); } bool MtpReadCapability::isEndOfParseTracksList() { return m_handler.data()->isEndOfParseTracksList(); } void MtpReadCapability::prepareToParseNextTrack() { if( m_handler ) m_handler.data()->prepareToParseNextTrack(); } void MtpReadCapability::nextTrackToParse() { if( m_handler ) m_handler.data()->nextTrackToParse(); } void MtpReadCapability::setAssociateTrack( const Meta::MediaDeviceTrackPtr track ) { if( m_handler ) m_handler.data()->setAssociateTrack( track ); } QString MtpReadCapability::libGetTitle( const Meta::MediaDeviceTrackPtr &track ) { return m_handler.data()->libGetTitle( track ); } QString MtpReadCapability::libGetAlbum( const Meta::MediaDeviceTrackPtr &track ) { return m_handler.data()->libGetAlbum( track ); } QString MtpReadCapability::libGetArtist( const Meta::MediaDeviceTrackPtr &track ) { return m_handler.data()->libGetArtist( track ); } QString MtpReadCapability::libGetAlbumArtist( const Meta::MediaDeviceTrackPtr &track ) { return m_handler.data()->libGetAlbumArtist( track ); } QString MtpReadCapability::libGetComposer( const Meta::MediaDeviceTrackPtr &track ) { return m_handler.data()->libGetComposer( track ); } QString MtpReadCapability::libGetGenre( const Meta::MediaDeviceTrackPtr &track ) { return m_handler.data()->libGetGenre( track ); } int MtpReadCapability::libGetYear( const Meta::MediaDeviceTrackPtr &track ) { return m_handler.data()->libGetYear( track ); } qint64 MtpReadCapability::libGetLength( const Meta::MediaDeviceTrackPtr &track ) { return m_handler.data()->libGetLength( track ); } int MtpReadCapability::libGetTrackNumber( const Meta::MediaDeviceTrackPtr &track ) { return m_handler.data()->libGetTrackNumber( track ); } QString MtpReadCapability::libGetComment( const Meta::MediaDeviceTrackPtr &track ) { return m_handler.data()->libGetComment( track ); } int MtpReadCapability::libGetDiscNumber( const Meta::MediaDeviceTrackPtr &track ) { return m_handler.data()->libGetDiscNumber( track ); } int MtpReadCapability::libGetBitrate( const Meta::MediaDeviceTrackPtr &track ) { return m_handler.data()->libGetBitrate( track ); } int MtpReadCapability::libGetSamplerate( const Meta::MediaDeviceTrackPtr &track ) { return m_handler.data()->libGetSamplerate( track ); } qreal MtpReadCapability::libGetBpm( const Meta::MediaDeviceTrackPtr &track ) { return m_handler.data()->libGetBpm( track ); } int MtpReadCapability::libGetFileSize( const Meta::MediaDeviceTrackPtr &track ) { return m_handler.data()->libGetFileSize( track ); } int MtpReadCapability::libGetPlayCount( const Meta::MediaDeviceTrackPtr &track ) { return m_handler.data()->libGetPlayCount( track ); } QDateTime MtpReadCapability::libGetLastPlayed( const Meta::MediaDeviceTrackPtr &track ) { return m_handler.data()->libGetLastPlayed( track ); } int MtpReadCapability::libGetRating( const Meta::MediaDeviceTrackPtr &track ) { return m_handler.data()->libGetRating( track ); } QString MtpReadCapability::libGetType( const Meta::MediaDeviceTrackPtr &track ) { return m_handler.data()->libGetType( track ); } -KUrl +QUrl MtpReadCapability::libGetPlayableUrl( const Meta::MediaDeviceTrackPtr &track ) { return m_handler.data()->libGetPlayableUrl( track ); } float MtpReadCapability::usedCapacity() const { return m_handler.data()->usedCapacity(); } float MtpReadCapability::totalCapacity() const { return m_handler.data()->totalCapacity(); } diff --git a/src/core-impl/collections/mtpcollection/handler/capabilities/MtpReadCapability.h b/src/core-impl/collections/mtpcollection/handler/capabilities/MtpReadCapability.h index d82d542166..e822badf96 100644 --- a/src/core-impl/collections/mtpcollection/handler/capabilities/MtpReadCapability.h +++ b/src/core-impl/collections/mtpcollection/handler/capabilities/MtpReadCapability.h @@ -1,81 +1,81 @@ /**************************************************************************************** * Copyright (c) 2009 Alejandro Wainzinger * * Copyright (c) 2009 Mark Kretschmann * * * * 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, see . * ****************************************************************************************/ #ifndef MTPREADCAPABILITY_H #define MTPREADCAPABILITY_H #include "mediadevicecollection_export.h" #include "ReadCapability.h" #include namespace Meta { class MtpHandler; } namespace Handler { class MtpReadCapability : public ReadCapability { Q_OBJECT public: MtpReadCapability( Meta::MtpHandler *handler ); virtual void prepareToParseTracks(); virtual bool isEndOfParseTracksList(); virtual void prepareToParseNextTrack(); virtual void nextTrackToParse(); virtual void setAssociateTrack( const Meta::MediaDeviceTrackPtr track ); virtual QString libGetTitle( const Meta::MediaDeviceTrackPtr &track ); virtual QString libGetAlbum( const Meta::MediaDeviceTrackPtr &track ); virtual QString libGetArtist( const Meta::MediaDeviceTrackPtr &track ); virtual QString libGetAlbumArtist( const Meta::MediaDeviceTrackPtr &track ); virtual QString libGetComposer( const Meta::MediaDeviceTrackPtr &track ); virtual QString libGetGenre( const Meta::MediaDeviceTrackPtr &track ); virtual int libGetYear( const Meta::MediaDeviceTrackPtr &track ); virtual qint64 libGetLength( const Meta::MediaDeviceTrackPtr &track ); virtual int libGetTrackNumber( const Meta::MediaDeviceTrackPtr &track ); virtual QString libGetComment( const Meta::MediaDeviceTrackPtr &track ); virtual int libGetDiscNumber( const Meta::MediaDeviceTrackPtr &track ); virtual int libGetBitrate( const Meta::MediaDeviceTrackPtr &track ); virtual int libGetSamplerate( const Meta::MediaDeviceTrackPtr &track ); virtual qreal libGetBpm( const Meta::MediaDeviceTrackPtr &track ); virtual int libGetFileSize( const Meta::MediaDeviceTrackPtr &track ); virtual int libGetPlayCount( const Meta::MediaDeviceTrackPtr &track ); virtual QDateTime libGetLastPlayed( const Meta::MediaDeviceTrackPtr &track ); virtual int libGetRating( const Meta::MediaDeviceTrackPtr &track ); virtual QString libGetType( const Meta::MediaDeviceTrackPtr &track ); - virtual KUrl libGetPlayableUrl( const Meta::MediaDeviceTrackPtr &track ); + virtual QUrl libGetPlayableUrl( const Meta::MediaDeviceTrackPtr &track ); virtual float usedCapacity() const; virtual float totalCapacity() const; private: QWeakPointer m_handler; }; } #endif diff --git a/src/core-impl/collections/nepomukcollection/NepomukCollection.cpp b/src/core-impl/collections/nepomukcollection/NepomukCollection.cpp index 77aef8ee8d..a0e3e4332c 100644 --- a/src/core-impl/collections/nepomukcollection/NepomukCollection.cpp +++ b/src/core-impl/collections/nepomukcollection/NepomukCollection.cpp @@ -1,101 +1,101 @@ /**************************************************************************************** * Copyright (c) 2012 Phalgun Guduthur * * Copyright (c) 2013 Edward Toroshchin * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "NepomukCollection" #include "NepomukCollection.h" #include "NepomukCache.h" #include "NepomukQueryMaker.h" #include "core/meta/Meta.h" #include "core/meta/support/MetaKeys.h" #include "core/support/Debug.h" #include #include #include #include #include namespace Collections { NepomukCollection::NepomukCollection() : m_cache( new NepomukCache( this ) ) { } NepomukCollection::~NepomukCollection() { } QueryMaker* NepomukCollection::queryMaker() { return new NepomukQueryMaker( this ); } QString NepomukCollection::uidUrlProtocol() const { return QLatin1String( "amarok-nepomuk" ); } QString NepomukCollection::collectionId() const { return QString( "%1://" ).arg( uidUrlProtocol() ); } QString NepomukCollection::prettyName() const { return i18n( "Nepomuk Collection" ); } KIcon NepomukCollection::icon() const { return KIcon( "nepomuk" ); } bool NepomukCollection::isWritable() const { // right now NepomukCollectionLocation isn't implemented, which deals with moving // and copying tracks into collection. So false until that is implemented. return false; } bool -NepomukCollection::possiblyContainsTrack( const KUrl & ) const +NepomukCollection::possiblyContainsTrack( const QUrl & ) const { return true; } Meta::TrackPtr -NepomukCollection::trackForUrl( const KUrl &url ) +NepomukCollection::trackForUrl( const QUrl &url ) { DEBUG_BLOCK debug() << url; return Meta::TrackPtr(); // TODO } } diff --git a/src/core-impl/collections/nepomukcollection/NepomukCollection.h b/src/core-impl/collections/nepomukcollection/NepomukCollection.h index c5d7a13035..4e03278ae8 100644 --- a/src/core-impl/collections/nepomukcollection/NepomukCollection.h +++ b/src/core-impl/collections/nepomukcollection/NepomukCollection.h @@ -1,63 +1,63 @@ /**************************************************************************************** * Copyright (c) 2012 Phalgun Guduthur * * Copyright (c) 2013 Edward Toroshchin * * * * 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, see . * ****************************************************************************************/ #ifndef NEPOMUKCOLLECTION_H #define NEPOMUKCOLLECTION_H #include "core/collections/Collection.h" #include namespace Collections { class NepomukCache; /** * This collection class interfaces with KDE Nepomuk semantic data storage. * * It connects to the underlying Soprano database interface and provides a * QueryMaker interface to the objects of type nfo:Audio stored there. */ class NepomukCollection : public Collection { Q_OBJECT public: NepomukCollection(); virtual ~NepomukCollection(); virtual QueryMaker *queryMaker(); virtual QString uidUrlProtocol() const; virtual QString collectionId() const; virtual QString prettyName() const; virtual KIcon icon() const; virtual bool isWritable() const; // TrackProvider methods - virtual bool possiblyContainsTrack( const KUrl &url ) const; - virtual Meta::TrackPtr trackForUrl( const KUrl &url ); + virtual bool possiblyContainsTrack( const QUrl &url ) const; + virtual Meta::TrackPtr trackForUrl( const QUrl &url ); NepomukCache *cache() const { return m_cache; } private: NepomukCache *m_cache; }; } //namespace Collections #endif // NEPOMUKCOLLECTION_H diff --git a/src/core-impl/collections/nepomukcollection/meta/NepomukTrack.cpp b/src/core-impl/collections/nepomukcollection/meta/NepomukTrack.cpp index 5cadc553d5..51b2d2c4ce 100644 --- a/src/core-impl/collections/nepomukcollection/meta/NepomukTrack.cpp +++ b/src/core-impl/collections/nepomukcollection/meta/NepomukTrack.cpp @@ -1,341 +1,341 @@ /**************************************************************************************** * Copyright (c) 2012 Phalgun Guduthur * * Copyright (c) 2013 Edward Toroshchin * * * * 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, see . * ****************************************************************************************/ #include "NepomukTrack.h" #include "NepomukCollection.h" #include "NepomukLabel.h" #include "core/meta/Meta.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include #include #include #include #include #include using namespace Meta; NepomukTrack::NepomukTrack( const QUrl &resUri, Collections::NepomukCollection *coll ) : m_filled( false ) , m_length( 0 ) , m_bitrate( 0 ) , m_trackNumber( 0 ) , m_discNumber( 0 ) , m_bpm( 0.0 ) , m_sampleRate( 0 ) , m_filesize( 0 ) , m_trackGain( 0.0 ) , m_trackPeakGain( 0.0 ) , m_albumGain( 0.0 ) , m_albumPeakGain( 0.0 ) , m_coll( coll ) , m_resourceUri( resUri ) { } NepomukTrack::~NepomukTrack() { } QString NepomukTrack::name() const { return m_name; } -KUrl +QUrl NepomukTrack::playableUrl() const { return m_playableUrl; } QString NepomukTrack::prettyUrl() const { return m_playableUrl.path(); } QString NepomukTrack::uidUrl() const { return m_resourceUri.toString(); } QString NepomukTrack::notPlayableReason() const { if( !m_playableUrl.isValid() ) return i18n( "Invalid URL" ); return QString(); } AlbumPtr NepomukTrack::album() const { return m_album; } ArtistPtr NepomukTrack::artist() const { return m_artist; } ComposerPtr NepomukTrack::composer() const { return m_composer; } GenrePtr NepomukTrack::genre() const { return m_genre; } YearPtr NepomukTrack::year() const { return m_year; } qreal NepomukTrack::bpm() const { return m_bpm; } QString NepomukTrack::comment() const { return m_comment; } qint64 NepomukTrack::length() const { return m_length; } int NepomukTrack::filesize() const { return m_filesize; } int NepomukTrack::sampleRate() const { return m_sampleRate; } int NepomukTrack::bitrate() const { return m_bitrate; } QDateTime NepomukTrack::createDate() const { return m_createDate; } QDateTime NepomukTrack::modifyDate() const { return m_modifyDate; } int NepomukTrack::trackNumber() const { return m_trackNumber; } int NepomukTrack::discNumber() const { return m_discNumber; } qreal NepomukTrack::replayGain( ReplayGainTag mode ) const { qreal gain = 0; switch( mode ) { case ReplayGain_Track_Gain : gain = m_trackGain; break; case ReplayGain_Track_Peak : gain = m_trackPeakGain; break; case ReplayGain_Album_Gain : gain = m_albumGain; if( gain == 0 ) gain = m_trackGain; break; case ReplayGain_Album_Peak : gain = m_albumPeakGain; if( gain == 0 ) gain = m_trackPeakGain; break; } return gain; } QString NepomukTrack::type() const { return m_type; } bool NepomukTrack::inCollection() const { return m_coll; } Collections::Collection* NepomukTrack::collection() const { return m_coll; } void NepomukTrack::addLabel( const Meta::LabelPtr &label ) { if( !label ) return; const NepomukLabel *nlabel = dynamic_cast(label.data()); if( nlabel ) { resource()->addTag( nlabel->tag() ); notifyObservers(); } else { addLabel( label->name() ); } } void NepomukTrack::addLabel( const QString &label ) { Nepomuk2::Tag tag; tag.setLabel( label ); resource()->addTag( tag ); notifyObservers(); } Meta::LabelList NepomukTrack::labels() const { LabelList result; foreach( const Nepomuk2::Tag &tag, resource()->tags() ) result << NepomukLabel::fromNepomukTag( m_coll, tag ); return result; } void NepomukTrack::removeLabel( const LabelPtr &label ) { const NepomukLabel *nlabel = dynamic_cast(label.data()); if( !nlabel ) return; resource()->removeProperty( Soprano::Vocabulary::NAO::hasTag(), nlabel->tag() ); notifyObservers(); } StatisticsPtr NepomukTrack::statistics() { return StatisticsPtr( this ); } int NepomukTrack::rating() const { return resource()->rating(); } void NepomukTrack::setRating( int newRating ) { resource()->setRating( newRating ); notifyObservers(); } QDateTime NepomukTrack::lastPlayed() const { return resource()->property( Nepomuk2::Vocabulary::NUAO::lastUsage() ).toDateTime(); } void NepomukTrack::setLastPlayed( const QDateTime &date ) { resource()->setProperty( Nepomuk2::Vocabulary::NUAO::lastUsage(), date ); notifyObservers(); } QDateTime NepomukTrack::firstPlayed() const { return resource()->property( Nepomuk2::Vocabulary::NUAO::firstUsage() ).toDateTime(); } void NepomukTrack::setFirstPlayed( const QDateTime &date ) { resource()->setProperty( Nepomuk2::Vocabulary::NUAO::firstUsage(), date ); notifyObservers(); } int NepomukTrack::playCount() const { return resource()->usageCount(); } void NepomukTrack::setPlayCount( int newPlayCount ) { resource()->setProperty( Nepomuk2::Vocabulary::NUAO::usageCount(), newPlayCount ); notifyObservers(); } Nepomuk2::Resource* NepomukTrack::resource() const { if( !m_resource ) m_resource.reset(new Nepomuk2::Resource( m_resourceUri )); return m_resource.data(); } diff --git a/src/core-impl/collections/nepomukcollection/meta/NepomukTrack.h b/src/core-impl/collections/nepomukcollection/meta/NepomukTrack.h index 9121dd0277..8055a7a955 100644 --- a/src/core-impl/collections/nepomukcollection/meta/NepomukTrack.h +++ b/src/core-impl/collections/nepomukcollection/meta/NepomukTrack.h @@ -1,167 +1,167 @@ /**************************************************************************************** * Copyright (c) 2012 Phalgun Guduthur * * Copyright (c) 2013 Edward Toroshchin * * * * 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, see . * ****************************************************************************************/ #ifndef NEPOMUKTRACK_H #define NEPOMUKTRACK_H #include "core/meta/Meta.h" #include "core/meta/Statistics.h" #include namespace Collections { class NepomukCollection; } namespace Nepomuk2 { class Resource; } namespace Meta { class NepomukTrack; /** * Represents a unit music track resource in Amarok */ class NepomukTrack : public Track, public Statistics { public: // construct a NepomukTrack out of a Nepomuk resource NepomukTrack( const QUrl &resUri, Collections::NepomukCollection *coll = 0 ); ~NepomukTrack(); virtual QString name() const; - virtual KUrl playableUrl() const; + virtual QUrl playableUrl() const; virtual QString prettyUrl() const; virtual QString uidUrl() const; virtual QString notPlayableReason() const; virtual AlbumPtr album() const; virtual ArtistPtr artist() const; virtual ComposerPtr composer() const; virtual GenrePtr genre() const; virtual YearPtr year() const; virtual LabelList labels() const; virtual qreal bpm() const; virtual QString comment() const; virtual qint64 length() const; virtual int filesize() const; virtual int sampleRate() const; virtual int bitrate() const; virtual QDateTime createDate() const; virtual QDateTime modifyDate() const; virtual int trackNumber() const; virtual int discNumber() const; virtual qreal replayGain( ReplayGainTag mode ) const; virtual QString type() const; virtual bool inCollection() const; virtual Collections::Collection *collection() const; virtual void addLabel( const LabelPtr &label ); virtual void addLabel( const QString &label ); virtual void removeLabel( const LabelPtr &label ); virtual StatisticsPtr statistics(); // Meta::Statistics methods // TODO: introduce scores into Nepomuk and implement score(), setScore() virtual int rating() const; virtual void setRating( int newRating ); virtual QDateTime lastPlayed() const; virtual void setLastPlayed( const QDateTime &date ); virtual QDateTime firstPlayed() const; virtual void setFirstPlayed( const QDateTime &date ); virtual int playCount() const; virtual void setPlayCount( int newPlayCount ); // NepomukTrack meta methods void setAlbum( AlbumPtr album ) { m_album = album; } void setArtist( ArtistPtr artist ) { m_artist = artist; } void setComposer( ComposerPtr composer ) { m_composer = composer; } void setGenre( GenrePtr genre ) { m_genre = genre; } void setYear( YearPtr year ) { m_year = year; } // NepomukTrack secondary metadata methods void setName( const QString &name ) { m_name = name; } void setType( const QString &type ) { m_type = type; } void setLength( const qint64 length ) { m_length = length; } void setBitrate( int rate ) { m_bitrate = rate; } void setTrackNumber( int trackNumber ) { m_trackNumber = trackNumber; } void setDiscNumber( int discNumber ) { m_discNumber = discNumber; } void setModifyDate( const QDateTime &modifyDate ) { m_modifyDate = modifyDate; } void setCreateDate( const QDateTime &createDate ) { m_createDate = createDate; } void setbpm( const qreal bpm ) { m_bpm = bpm; } void setComment( const QString &comment ) { m_comment = comment; } void setSampleRate( int sampleRate ) { m_sampleRate = sampleRate; } void setFilesize( int filesize ) { m_filesize = filesize; } void setTrackGain( qreal trackGain ) { m_trackGain = trackGain; } void setTrackPeakGain( qreal trackPeakGain ) { m_trackPeakGain = trackPeakGain; } void setAlbumGain( qreal albumGain ) { m_albumGain = albumGain; } void setAlbumPeakGain( qreal albumPeakGain ) { m_albumPeakGain = albumPeakGain; } - void setPlayableUrl( const KUrl &url ) { m_playableUrl = url; } + void setPlayableUrl( const QUrl &url ) { m_playableUrl = url; } bool isFilled(){ return m_filled; } - void fill( const QString &name, const KUrl &url, Collections::NepomukCollection *coll ) + void fill( const QString &name, const QUrl &url, Collections::NepomukCollection *coll ) { m_name = name; m_playableUrl = url; m_coll = coll; m_filled = true; } Nepomuk2::Resource *resource() const; private: bool m_filled; ArtistPtr m_artist; GenrePtr m_genre; ComposerPtr m_composer; AlbumPtr m_album; YearPtr m_year; LabelList m_labellist; - KUrl m_playableUrl; + QUrl m_playableUrl; QString m_name; QString m_type; qint64 m_length; int m_bitrate; int m_trackNumber; int m_discNumber; QDateTime m_modifyDate; QDateTime m_createDate; qreal m_bpm; QString m_comment; int m_sampleRate; int m_filesize; double m_trackGain; double m_trackPeakGain; double m_albumGain; double m_albumPeakGain; Collections::NepomukCollection *m_coll; mutable QScopedPointer m_resource; QUrl m_resourceUri; }; } #endif /*NEPOMUKTRACK_H*/ diff --git a/src/core-impl/collections/playdarcollection/PlaydarCollection.cpp b/src/core-impl/collections/playdarcollection/PlaydarCollection.cpp index e4d5acffe4..d4e07ff4c5 100644 --- a/src/core-impl/collections/playdarcollection/PlaydarCollection.cpp +++ b/src/core-impl/collections/playdarcollection/PlaydarCollection.cpp @@ -1,375 +1,375 @@ /**************************************************************************************** * Copyright (c) 2010 Andrew Coder * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "PlaydarCollection" #include "PlaydarCollection.h" #include "core/collections/Collection.h" #include "core-impl/collections/support/MemoryCollection.h" #include "core-impl/collections/support/MemoryQueryMaker.h" #include "core-impl/collections/support/CollectionManager.h" #include "core-impl/meta/proxy/MetaProxy.h" #include "PlaydarQueryMaker.h" #include "support/Controller.h" #include "support/ProxyResolver.h" #include "core/support/Debug.h" #include #include #include #include namespace Collections { AMAROK_EXPORT_COLLECTION( PlaydarCollectionFactory, playdarcollection ) PlaydarCollectionFactory::PlaydarCollectionFactory( QObject* parent, const QVariantList &args ) : CollectionFactory( parent, args ) , m_controller( 0 ) , m_collectionIsManaged( false ) { m_info = KPluginInfo( "amarok_collection-playdarcollection.desktop", "services" ); DEBUG_BLOCK } PlaydarCollectionFactory::~PlaydarCollectionFactory() { DEBUG_BLOCK CollectionManager::instance()->removeTrackProvider( m_collection.data() ); delete m_collection.data(); delete m_controller; } void PlaydarCollectionFactory::init() { DEBUG_BLOCK m_controller = new Playdar::Controller( this ); connect( m_controller, SIGNAL(playdarReady()), this, SLOT(playdarReady()) ); connect( m_controller, SIGNAL(playdarError(Playdar::Controller::ErrorState)), this, SLOT(slotPlaydarError(Playdar::Controller::ErrorState)) ); checkStatus(); m_collection = new PlaydarCollection; connect( m_collection.data(), SIGNAL(remove()), this, SLOT(collectionRemoved()) ); CollectionManager::instance()->addTrackProvider( m_collection.data() ); m_initialized = true; } void PlaydarCollectionFactory::checkStatus() { m_controller->status(); } void PlaydarCollectionFactory::playdarReady() { DEBUG_BLOCK if( !m_collection ) { m_collection = new PlaydarCollection(); connect( m_collection.data(), SIGNAL(remove()), this, SLOT(collectionRemoved()) ); } if( !m_collectionIsManaged ) { m_collectionIsManaged = true; emit newCollection( m_collection.data() ); } } void PlaydarCollectionFactory::slotPlaydarError( Playdar::Controller::ErrorState error ) { // DEBUG_BLOCK if( error == Playdar::Controller::ErrorState( 1 ) ) { if( m_collection && !m_collectionIsManaged ) CollectionManager::instance()->removeTrackProvider( m_collection.data() ); QTimer::singleShot( 10 * 60 * 1000, this, SLOT(checkStatus()) ); } } void PlaydarCollectionFactory::collectionRemoved() { DEBUG_BLOCK m_collectionIsManaged = false; QTimer::singleShot( 10000, this, SLOT(checkStatus()) ); } PlaydarCollection::PlaydarCollection() : m_collectionId( i18n( "Playdar Collection" ) ) , m_memoryCollection( new MemoryCollection ) { DEBUG_BLOCK } PlaydarCollection::~PlaydarCollection() { DEBUG_BLOCK } QueryMaker* PlaydarCollection::queryMaker() { DEBUG_BLOCK PlaydarQueryMaker *freshQueryMaker = new PlaydarQueryMaker( this ); connect( freshQueryMaker, SIGNAL(playdarError(Playdar::Controller::ErrorState)), this, SLOT(slotPlaydarError(Playdar::Controller::ErrorState)) ); return freshQueryMaker; } Playlists::UserPlaylistProvider* PlaydarCollection::userPlaylistProvider() { DEBUG_BLOCK return 0; } QString PlaydarCollection::uidUrlProtocol() const { return QString( "playdar" ); } QString PlaydarCollection::collectionId() const { return m_collectionId; } QString PlaydarCollection::prettyName() const { return collectionId(); } KIcon PlaydarCollection::icon() const { return KIcon( "network-server" ); } bool PlaydarCollection::isWritable() const { DEBUG_BLOCK return false; } bool PlaydarCollection::isOrganizable() const { DEBUG_BLOCK return false; } bool - PlaydarCollection::possiblyContainsTrack( const KUrl &url ) const + PlaydarCollection::possiblyContainsTrack( const QUrl &url ) const { DEBUG_BLOCK - if( url.protocol() == uidUrlProtocol() && + if( url.scheme() == uidUrlProtocol() && url.hasQueryItem( QString( "artist" ) ) && url.hasQueryItem( QString( "album" ) ) && url.hasQueryItem( QString( "title" ) ) ) return true; else return false; } Meta::TrackPtr - PlaydarCollection::trackForUrl( const KUrl &url ) + PlaydarCollection::trackForUrl( const QUrl &url ) { DEBUG_BLOCK m_memoryCollection->acquireReadLock(); if( m_memoryCollection->trackMap().contains( url.url() ) ) { Meta::TrackPtr track = m_memoryCollection->trackMap().value( url.url() ); m_memoryCollection->releaseLock(); return track; } else { m_memoryCollection->releaseLock(); MetaProxy::TrackPtr proxyTrack( new MetaProxy::Track( url ) ); - proxyTrack->setArtist( url.queryItem( "artist" ) ); - proxyTrack->setAlbum( url.queryItem( "album" ) ); - proxyTrack->setTitle( url.queryItem( "title" ) ); + proxyTrack->setArtist( QUrlQuery(url).queryItemValue( "artist" ) ); + proxyTrack->setAlbum( QUrlQuery(url).queryItemValue( "album" ) ); + proxyTrack->setTitle( QUrlQuery(url).queryItemValue( "title" ) ); Playdar::ProxyResolver *proxyResolver = new Playdar::ProxyResolver( this, url, proxyTrack ); connect( proxyResolver, SIGNAL(playdarError(Playdar::Controller::ErrorState)), this, SLOT(slotPlaydarError(Playdar::Controller::ErrorState)) ); return Meta::TrackPtr::staticCast( proxyTrack ); } } bool PlaydarCollection::hasCapabilityInterface( Capabilities::Capability::Type type ) const { //TODO: Make this work once capabilities are set. Q_UNUSED( type ); return false; } Capabilities::Capability* PlaydarCollection::createCapabilityInterface( Capabilities::Capability::Type type ) { //TODO: Make this work once capabilities are set. Q_UNUSED( type ); return 0; } void PlaydarCollection::addNewTrack( Meta::PlaydarTrackPtr track ) { DEBUG_BLOCK m_memoryCollection->acquireReadLock(); if( !m_memoryCollection->trackMap().contains( track->uidUrl() ) ) { m_memoryCollection->releaseLock(); m_memoryCollection->acquireWriteLock(); Meta::PlaydarArtistPtr artistPtr; if( m_memoryCollection->artistMap().contains( track->artist()->name() ) ) { Meta::ArtistPtr artist = m_memoryCollection->artistMap().value( track->artist()->name() ); artistPtr = Meta::PlaydarArtistPtr::staticCast( artist ); } else { artistPtr = track->playdarArtist(); Meta::ArtistPtr artist = Meta::ArtistPtr::staticCast( artistPtr ); m_memoryCollection->addArtist( artist ); } artistPtr->addTrack( track ); track->setArtist( artistPtr ); Meta::PlaydarAlbumPtr albumPtr; if( m_memoryCollection->albumMap().contains( track->album()->name(), artistPtr->name() ) ) { Meta::AlbumPtr album = m_memoryCollection->albumMap().value( track->album()->name(), artistPtr->name() ); albumPtr = Meta::PlaydarAlbumPtr::staticCast( album ); } else { albumPtr = track->playdarAlbum(); albumPtr->setAlbumArtist( artistPtr ); artistPtr->addAlbum( albumPtr ); Meta::AlbumPtr album = Meta::AlbumPtr::staticCast( albumPtr ); m_memoryCollection->addAlbum( album ); } albumPtr->addTrack( track ); track->setAlbum( albumPtr ); Meta::PlaydarGenrePtr genrePtr; if( m_memoryCollection->genreMap().contains( track->genre()->name() ) ) { Meta::GenrePtr genre = m_memoryCollection->genreMap().value( track->genre()->name() ); genrePtr = Meta::PlaydarGenrePtr::staticCast( genre ); } else { genrePtr = track->playdarGenre(); Meta::GenrePtr genre = Meta::GenrePtr::staticCast( genrePtr ); m_memoryCollection->addGenre( genre ); } genrePtr->addTrack( track ); track->setGenre( genrePtr ); Meta::PlaydarComposerPtr composerPtr; if( m_memoryCollection->composerMap().contains( track->composer()->name() ) ) { Meta::ComposerPtr composer = m_memoryCollection->composerMap().value( track->composer()->name() ); composerPtr = Meta::PlaydarComposerPtr::staticCast( composer ); } else { composerPtr = track->playdarComposer(); Meta::ComposerPtr composer = Meta::ComposerPtr::staticCast( composerPtr ); m_memoryCollection->addComposer( composer ); } composerPtr->addTrack( track ); track->setComposer( composerPtr ); Meta::PlaydarYearPtr yearPtr; if( m_memoryCollection->yearMap().contains( track->year()->year() ) ) { Meta::YearPtr year = m_memoryCollection->yearMap().value( track->year()->year() ); yearPtr = Meta::PlaydarYearPtr::staticCast( year ); } else { yearPtr = track->playdarYear(); Meta::YearPtr year = Meta::YearPtr::staticCast( yearPtr ); m_memoryCollection->addYear( year ); } yearPtr->addTrack( track ); track->setYear( yearPtr ); m_memoryCollection->addTrack( Meta::TrackPtr::staticCast( track ) ); foreach( Meta::PlaydarLabelPtr label, track->playdarLabels() ) { m_memoryCollection->addLabelToTrack( Meta::LabelPtr::staticCast( label ), Meta::TrackPtr::staticCast( track ) ); } m_memoryCollection->releaseLock(); emit updated(); } else m_memoryCollection->releaseLock(); } QSharedPointer< MemoryCollection > PlaydarCollection::memoryCollection() { return m_memoryCollection; } void PlaydarCollection::slotPlaydarError( Playdar::Controller::ErrorState error ) { if( error == Playdar::Controller::ErrorState( 1 ) ) emit remove(); } } diff --git a/src/core-impl/collections/playdarcollection/PlaydarCollection.h b/src/core-impl/collections/playdarcollection/PlaydarCollection.h index 1a7ec89d87..6d72e39a63 100644 --- a/src/core-impl/collections/playdarcollection/PlaydarCollection.h +++ b/src/core-impl/collections/playdarcollection/PlaydarCollection.h @@ -1,101 +1,101 @@ /**************************************************************************************** * Copyright (c) 2010 Andrew Coder * * * * 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, see . * ****************************************************************************************/ #ifndef PLAYDAR_COLLECTION_H #define PLAYDAR_COLLECTION_H #include "core/collections/Collection.h" #include "core-impl/collections/support/MemoryCollection.h" #include "PlaydarQueryMaker.h" #include "PlaydarMeta.h" #include "support/Controller.h" #include "support/ProxyResolver.h" #include #include #include #include namespace Collections { class QueryMaker; class PlaydarCollectionFactory : public CollectionFactory { Q_OBJECT public: PlaydarCollectionFactory( QObject* parent, const QVariantList &args ); virtual ~PlaydarCollectionFactory(); virtual void init(); private Q_SLOTS: void checkStatus(); void playdarReady(); void slotPlaydarError( Playdar::Controller::ErrorState error ); void collectionRemoved(); private: Playdar::Controller* m_controller; QWeakPointer< PlaydarCollection > m_collection; bool m_collectionIsManaged; }; class PlaydarCollection : public Collection { Q_OBJECT public: PlaydarCollection(); ~PlaydarCollection(); QueryMaker* queryMaker(); Playlists::UserPlaylistProvider* userPlaylistProvider(); QString uidUrlProtocol() const; QString collectionId() const; QString prettyName() const; KIcon icon() const; bool isWritable() const; bool isOrganizable() const; //Methods from Collections::TrackProvider - bool possiblyContainsTrack( const KUrl &url ) const; - Meta::TrackPtr trackForUrl( const KUrl &url ); + bool possiblyContainsTrack( const QUrl &url ) const; + Meta::TrackPtr trackForUrl( const QUrl &url ); //Methods from Collections::CollectionBase bool hasCapabilityInterface( Capabilities::Capability::Type type ) const; Capabilities::Capability* createCapabilityInterface( Capabilities::Capability::Type type ); //PlaydarCollection-specific void addNewTrack( Meta::PlaydarTrackPtr track ); QSharedPointer< MemoryCollection > memoryCollection(); private Q_SLOTS: void slotPlaydarError( Playdar::Controller::ErrorState error ); private: QString m_collectionId; QSharedPointer< MemoryCollection > m_memoryCollection; QList< QWeakPointer< Playdar::ProxyResolver > > m_proxyResolverList; }; } #endif diff --git a/src/core-impl/collections/playdarcollection/PlaydarMeta.cpp b/src/core-impl/collections/playdarcollection/PlaydarMeta.cpp index 2db789a900..eadd46e4af 100644 --- a/src/core-impl/collections/playdarcollection/PlaydarMeta.cpp +++ b/src/core-impl/collections/playdarcollection/PlaydarMeta.cpp @@ -1,667 +1,667 @@ /**************************************************************************************** * Copyright (c) 2010 Andrew Coder * * * * 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, see . * ****************************************************************************************/ #include "PlaydarMeta.h" #include "amarokconfig.h" #include "core/meta/Meta.h" #include "core-impl/meta/default/DefaultMetaTypes.h" #include "core-impl/support/UrlStatisticsStore.h" #include "covermanager/CoverFetcher.h" #include "covermanager/CoverCache.h" #include "PlaydarCollection.h" #include #include #include #include #include -#include +#include namespace Collections { class Collection; class PlaydarCollection; } Meta::PlaydarTrack::PlaydarTrack( QString &sid, QString &playableUrl, QString &name, QString &artist, QString &album, QString &mimetype, double score, qint64 length, int bitrate, int filesize, QString &source ) : m_album( new PlaydarAlbum( album ) ) , m_artist( new PlaydarArtist( artist ) ) , m_composer( new PlaydarComposer( QString( "" ) ) ) , m_genre( new PlaydarGenre( QString( "" ) ) ) , m_year( new PlaydarYear( QString( "" ) ) ) , m_labelList( ) , m_sid( sid ) , m_uidUrl( ) , m_playableUrl( playableUrl ) , m_name( name ) , m_mimetype( mimetype ) , m_score( score ) , m_length( length ) , m_bitrate( bitrate ) , m_filesize( filesize ) , m_trackNumber( 0 ) , m_discNumber( 0 ) , m_createDate( QDateTime::currentDateTime() ) , m_comment( QString( "" ) ) , m_source( source ) { m_uidUrl.setProtocol( QString( "playdar" ) ); m_uidUrl.addPath( source ); m_uidUrl.addQueryItem( QString( "artist" ), artist ); m_uidUrl.addQueryItem( QString( "album" ), album ); m_uidUrl.addQueryItem( QString( "title" ), name ); m_statsStore = new UrlStatisticsStore( this ); } Meta::PlaydarTrack::~PlaydarTrack() { //Do nothing... } QString Meta::PlaydarTrack::name() const { return m_name; } -KUrl +QUrl Meta::PlaydarTrack::playableUrl() const { - return KUrl( m_playableUrl ); + return QUrl( m_playableUrl ); } QString Meta::PlaydarTrack::prettyUrl() const { return uidUrl(); } QString Meta::PlaydarTrack::uidUrl() const { return m_uidUrl.url(); } QString Meta::PlaydarTrack::sid() const { return m_sid; } QString Meta::PlaydarTrack::notPlayableReason() const { if( !m_collection.data() ) return i18n( "Source collection removed" ); return QString(); } Meta::AlbumPtr Meta::PlaydarTrack::album() const { return AlbumPtr::staticCast( m_album ); } Meta::ArtistPtr Meta::PlaydarTrack::artist() const { return ArtistPtr::staticCast( m_artist ); } Meta::ComposerPtr Meta::PlaydarTrack::composer() const { return ComposerPtr::staticCast( m_composer ); } Meta::GenrePtr Meta::PlaydarTrack::genre() const { return GenrePtr::staticCast( m_genre ); } Meta::YearPtr Meta::PlaydarTrack::year() const { return YearPtr::staticCast( m_year ); } Meta::LabelList Meta::PlaydarTrack::labels() const { Meta::LabelList labelList; foreach( const PlaydarLabelPtr &label, m_labelList ) { labelList.append( LabelPtr::staticCast( label ) ); } return labelList; } qreal Meta::PlaydarTrack::bpm() const { /** TODO: Can we do better here? */ return -1.0; } QString Meta::PlaydarTrack::comment() const { return m_comment; } double Meta::PlaydarTrack::score() const { return m_score; } qint64 Meta::PlaydarTrack::length() const { return m_length; } int Meta::PlaydarTrack::filesize() const { return m_filesize; } int Meta::PlaydarTrack::sampleRate() const { return 0; } int Meta::PlaydarTrack::bitrate() const { return m_bitrate; } QDateTime Meta::PlaydarTrack::createDate() const { return m_createDate; } int Meta::PlaydarTrack::trackNumber() const { return m_trackNumber; } int Meta::PlaydarTrack::discNumber() const { return m_discNumber; } QString Meta::PlaydarTrack::type() const { return QString( "stream" ); } QString Meta::PlaydarTrack::mimetype() const { return m_mimetype; } bool Meta::PlaydarTrack::inCollection() const { return m_collection.data(); } Collections::Collection* Meta::PlaydarTrack::collection() const { return m_collection.data(); } QString Meta::PlaydarTrack::cachedLyrics() const { return QString( "" ); } void Meta::PlaydarTrack::setCachedLyrics( const QString &lyrics ) { Q_UNUSED( lyrics ); } void Meta::PlaydarTrack::addLabel( const QString &label ) { PlaydarLabelPtr newLabel( new PlaydarLabel( label ) ); m_labelList.append( newLabel ); } void Meta::PlaydarTrack::addLabel( const LabelPtr &label ) { PlaydarLabelPtr newLabel( new PlaydarLabel( label->name() ) ); m_labelList.append( newLabel ); } void Meta::PlaydarTrack::removeLabel( const LabelPtr &label ) { foreach( const PlaydarLabelPtr &labelPtr, m_labelList ) { if( labelPtr->name() == label->name() ) { m_labelList.removeOne( labelPtr ); return; } } } Meta::StatisticsPtr Meta::PlaydarTrack::statistics() { return m_statsStore; } QString Meta::PlaydarTrack::source() const { return m_source; } void Meta::PlaydarTrack::addToCollection( Collections::PlaydarCollection *collection ) { m_collection = collection; if( m_collection.data() ) { PlaydarTrackPtr sharedThis( this ); m_collection.data()->addNewTrack( sharedThis ); } } void Meta::PlaydarTrack::setAlbum( PlaydarAlbumPtr album ) { m_album = album; } void Meta::PlaydarTrack::setArtist( PlaydarArtistPtr artist ) { m_artist = artist; } void Meta::PlaydarTrack::setComposer( PlaydarComposerPtr composer ) { m_composer = composer; } void Meta::PlaydarTrack::setGenre( PlaydarGenrePtr genre ) { m_genre = genre; } void Meta::PlaydarTrack::setYear( PlaydarYearPtr year ) { m_year = year; } Meta::PlaydarAlbumPtr Meta::PlaydarTrack::playdarAlbum() { return m_album; } Meta::PlaydarArtistPtr Meta::PlaydarTrack::playdarArtist() { return m_artist; } Meta::PlaydarComposerPtr Meta::PlaydarTrack::playdarComposer() { return m_composer; } Meta::PlaydarGenrePtr Meta::PlaydarTrack::playdarGenre() { return m_genre; } Meta::PlaydarYearPtr Meta::PlaydarTrack::playdarYear() { return m_year; } Meta::PlaydarLabelList Meta::PlaydarTrack::playdarLabels() { return m_labelList; } Meta::PlaydarArtist::PlaydarArtist( const QString &name ) : m_name( name ) , m_tracks( ) , m_albums( ) { //Do nothing... } Meta::PlaydarArtist::~PlaydarArtist() { //Do nothing... } QString Meta::PlaydarArtist::name() const { return m_name; } Meta::TrackList Meta::PlaydarArtist::tracks() { return m_tracks; } Meta::AlbumList Meta::PlaydarArtist::albums() { return m_albums; } void Meta::PlaydarArtist::addTrack( PlaydarTrackPtr newTrack ) { m_tracks.append( TrackPtr::staticCast( newTrack ) ); } void Meta::PlaydarArtist::addAlbum( PlaydarAlbumPtr newAlbum ) { m_albums.append( AlbumPtr::staticCast( newAlbum ) ); } Meta::PlaydarAlbum::PlaydarAlbum( const QString &name ) : m_name( name ) , m_tracks( ) , m_isCompilation( false ) , m_albumArtist( 0 ) , m_suppressImageAutoFetch( false ) , m_triedToFetchCover( false ) { //Do nothing... } Meta::PlaydarAlbum::~PlaydarAlbum() { CoverCache::invalidateAlbum( this ); } bool Meta::PlaydarAlbum::isCompilation() const { return m_isCompilation; } QString Meta::PlaydarAlbum::name() const { return m_name; } bool Meta::PlaydarAlbum::hasAlbumArtist() const { if( !m_albumArtist.isNull() ) return true; else return false; } Meta::ArtistPtr Meta::PlaydarAlbum::albumArtist() const { return m_albumArtist; } Meta::TrackList Meta::PlaydarAlbum::tracks() { return m_tracks; } bool Meta::PlaydarAlbum::hasImage( int size ) const { Q_UNUSED( size ); if( !m_cover.isNull() ) return true; else return false; } QImage Meta::PlaydarAlbum::image( int size ) const { if ( m_cover.isNull() ) { if( !m_suppressImageAutoFetch && !m_name.isEmpty() && !m_triedToFetchCover && AmarokConfig::autoGetCoverArt() ) { m_triedToFetchCover = true; CoverFetcher::instance()->queueAlbum( Meta::AlbumPtr(const_cast(this)) ); } return Meta::Album::image( size ); } return size <= 1 ? m_cover : m_cover.scaled( size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation ); } -KUrl +QUrl Meta::PlaydarAlbum::imageLocation( int size ) { if( !m_cover.isNull() ) - return KUrl(); + return QUrl(); return Meta::Album::imageLocation( size ); } bool Meta::PlaydarAlbum::canUpdateImage() const { return true; } void Meta::PlaydarAlbum::setImage( const QImage &image ) { m_cover = image; CoverCache::invalidateAlbum( this ); } void Meta::PlaydarAlbum::removeImage() { m_cover = QImage(); CoverCache::invalidateAlbum( this ); } void Meta::PlaydarAlbum::setSuppressImageAutoFetch( const bool suppress ) { m_suppressImageAutoFetch = suppress; } bool Meta::PlaydarAlbum::suppressImageAutoFetch() const { return m_suppressImageAutoFetch; } void Meta::PlaydarAlbum::addTrack( PlaydarTrackPtr newTrack ) { m_tracks.append( TrackPtr::staticCast( newTrack ) ); } void Meta::PlaydarAlbum::setAlbumArtist( PlaydarArtistPtr newArtist ) { m_albumArtist = ArtistPtr::staticCast( newArtist ); } Meta::PlaydarComposer::PlaydarComposer( const QString &name ) : m_name( name ) , m_tracks( ) { //Do nothing... } Meta::PlaydarComposer::~PlaydarComposer() { //Do nothing... } QString Meta::PlaydarComposer::name() const { return m_name; } Meta::TrackList Meta::PlaydarComposer::tracks() { return m_tracks; } void Meta::PlaydarComposer::addTrack( PlaydarTrackPtr newTrack ) { m_tracks.append( Meta::TrackPtr::staticCast( newTrack ) ); } Meta::PlaydarGenre::PlaydarGenre( const QString &name ) : m_name( name ) , m_tracks( ) { //Do nothing... } Meta::PlaydarGenre::~PlaydarGenre() { //Do nothing... } QString Meta::PlaydarGenre::name() const { return m_name; } Meta::TrackList Meta::PlaydarGenre::tracks() { return m_tracks; } void Meta::PlaydarGenre::addTrack( PlaydarTrackPtr newTrack ) { m_tracks.append( Meta::TrackPtr::staticCast( newTrack ) ); } Meta::PlaydarYear::PlaydarYear( const QString &name ) : m_name( name ) , m_tracks( ) { //Do nothing... } Meta::PlaydarYear::~PlaydarYear() { //Do nothing... } QString Meta::PlaydarYear::name() const { return m_name; } Meta::TrackList Meta::PlaydarYear::tracks() { return m_tracks; } void Meta::PlaydarYear::addTrack( PlaydarTrackPtr newTrack ) { m_tracks.append( Meta::TrackPtr::staticCast( newTrack ) ); } Meta::PlaydarLabel::PlaydarLabel( const QString &name ) : m_name( name ) , m_tracks( ) { //Do nothing... } Meta::PlaydarLabel::~PlaydarLabel() { //Do nothing... } QString Meta::PlaydarLabel::name() const { return m_name; } void Meta::PlaydarLabel::addTrack( PlaydarTrackPtr newTrack ) { m_tracks.append( Meta::TrackPtr::staticCast( newTrack ) ); } diff --git a/src/core-impl/collections/playdarcollection/PlaydarMeta.h b/src/core-impl/collections/playdarcollection/PlaydarMeta.h index 48f9c570ae..23751ea80a 100644 --- a/src/core-impl/collections/playdarcollection/PlaydarMeta.h +++ b/src/core-impl/collections/playdarcollection/PlaydarMeta.h @@ -1,282 +1,282 @@ /**************************************************************************************** * Copyright (c) 2010 Andrew Coder * * * * 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, see . * ****************************************************************************************/ #ifndef PLAYDAR_META_H #define PLAYDAR_META_H #include "core/meta/Meta.h" #include #include #include #include #include -#include +#include namespace Collections { class Collection; class PlaydarCollection; } namespace Meta { class PlaydarTrack; class PlaydarArtist; class PlaydarAlbum; class PlaydarGenre; class PlaydarComposer; class PlaydarYear; class PlaydarLabel; typedef KSharedPtr< PlaydarTrack > PlaydarTrackPtr; typedef QList< PlaydarTrackPtr > PlaydarTrackList; typedef KSharedPtr< PlaydarArtist > PlaydarArtistPtr; typedef QList< PlaydarArtistPtr > PlaydarArtistList; typedef KSharedPtr< PlaydarAlbum > PlaydarAlbumPtr; typedef QList< PlaydarAlbumPtr > PlaydarAlbumList; typedef KSharedPtr< PlaydarComposer > PlaydarComposerPtr; typedef QList< PlaydarComposerPtr > PlaydarComposerList; typedef KSharedPtr< PlaydarGenre > PlaydarGenrePtr; typedef QList< PlaydarGenrePtr > PlaydarGenreList; typedef KSharedPtr< PlaydarYear > PlaydarYearPtr; typedef QList< PlaydarYearPtr > PlaydarYearList; typedef KSharedPtr< PlaydarLabel > PlaydarLabelPtr; typedef QList< PlaydarLabelPtr > PlaydarLabelList; class PlaydarTrack : public Track { public: PlaydarTrack( QString &sid, QString &playableUrl, QString &name, QString &artist, QString &album, QString &mimetype, double score, qint64 length, int bitrate, int filesize, QString &source ); ~PlaydarTrack(); QString name() const; - KUrl playableUrl() const; + QUrl playableUrl() const; QString prettyUrl() const; QString uidUrl() const; QString sid() const; QString notPlayableReason() const; AlbumPtr album() const; ArtistPtr artist() const; ComposerPtr composer() const; GenrePtr genre() const; YearPtr year() const; LabelList labels() const; qreal bpm() const; QString comment() const; double score() const; qint64 length() const; int filesize() const; int sampleRate() const; int bitrate() const; QDateTime createDate() const; int trackNumber() const; int discNumber() const; QString type() const; bool inCollection() const; Collections::Collection* collection() const; QString cachedLyrics() const; void setCachedLyrics( const QString &lyrics ); void addLabel( const QString &label ); void addLabel( const LabelPtr &label ); void removeLabel( const LabelPtr &label ); StatisticsPtr statistics(); //PlaydarTrack-specific: QString source() const; QString mimetype() const; void addToCollection( Collections::PlaydarCollection *collection ); void setAlbum( PlaydarAlbumPtr album ); void setArtist( PlaydarArtistPtr artist ); void setComposer( PlaydarComposerPtr composer ); void setGenre( PlaydarGenrePtr genre ); void setYear( PlaydarYearPtr year ); PlaydarAlbumPtr playdarAlbum(); PlaydarArtistPtr playdarArtist(); PlaydarComposerPtr playdarComposer(); PlaydarGenrePtr playdarGenre(); PlaydarYearPtr playdarYear(); PlaydarLabelList playdarLabels(); private: QWeakPointer< Collections::PlaydarCollection > m_collection; PlaydarAlbumPtr m_album; PlaydarArtistPtr m_artist; PlaydarComposerPtr m_composer; PlaydarGenrePtr m_genre; PlaydarYearPtr m_year; PlaydarLabelList m_labelList; Meta::StatisticsPtr m_statsStore; QString m_sid; - KUrl m_uidUrl; + QUrl m_uidUrl; QString m_playableUrl; QString m_name; QString m_mimetype; double m_score; qint64 m_length; int m_bitrate; int m_filesize; int m_trackNumber; int m_discNumber; QDateTime m_createDate; QString m_comment; QString m_source; }; class PlaydarArtist : public Artist { public: PlaydarArtist( const QString &name ); ~PlaydarArtist(); QString name() const; TrackList tracks(); AlbumList albums(); void addTrack( PlaydarTrackPtr newTrack ); void addAlbum( PlaydarAlbumPtr newAlbum ); private: QString m_name; TrackList m_tracks; AlbumList m_albums; }; class PlaydarAlbum : public Album { public: PlaydarAlbum( const QString &name ); ~PlaydarAlbum(); bool isCompilation() const; QString name() const; bool hasAlbumArtist() const; ArtistPtr albumArtist() const; TrackList tracks(); bool hasImage( int size = 0 ) const; QImage image( int size = 0 ) const; - KUrl imageLocation( int size = 0 ); + QUrl imageLocation( int size = 0 ); bool canUpdateImage() const; void setImage( const QImage &image ); void removeImage(); void setSuppressImageAutoFetch( const bool suppress ); bool suppressImageAutoFetch() const; void addTrack( PlaydarTrackPtr newTrack ); void setAlbumArtist( PlaydarArtistPtr newAlbumArtist ); private: QString m_name; TrackList m_tracks; bool m_isCompilation; ArtistPtr m_albumArtist; bool m_suppressImageAutoFetch; mutable bool m_triedToFetchCover; mutable QImage m_cover; }; class PlaydarComposer : public Composer { public: PlaydarComposer( const QString &name ); ~PlaydarComposer(); QString name() const; TrackList tracks(); void addTrack( PlaydarTrackPtr newTrack ); private: QString m_name; TrackList m_tracks; }; class PlaydarGenre : public Genre { public: PlaydarGenre( const QString &name ); ~PlaydarGenre(); QString name() const; TrackList tracks(); void addTrack( PlaydarTrackPtr newTrack ); private: QString m_name; TrackList m_tracks; }; class PlaydarYear : public Year { public: PlaydarYear( const QString &name ); ~PlaydarYear(); QString name() const; TrackList tracks(); void addTrack( PlaydarTrackPtr newTrack ); private: QString m_name; TrackList m_tracks; }; class PlaydarLabel : public Label { public: PlaydarLabel( const QString &name ); ~PlaydarLabel(); QString name() const; void addTrack( PlaydarTrackPtr newTrack ); private: QString m_name; TrackList m_tracks; }; } #endif /* PLAYDARMETA_H */ diff --git a/src/core-impl/collections/playdarcollection/support/Controller.cpp b/src/core-impl/collections/playdarcollection/support/Controller.cpp index d75e88ea65..d584ac168a 100644 --- a/src/core-impl/collections/playdarcollection/support/Controller.cpp +++ b/src/core-impl/collections/playdarcollection/support/Controller.cpp @@ -1,204 +1,205 @@ /**************************************************************************************** * Copyright (c) 2010 Andrew Coder * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "Playdar::Controller" #include "Controller.h" #include "Query.h" #include "core/support/Debug.h" #include -#include +#include #include #include #include #include #include namespace Playdar { Controller::Controller( bool queriesShouldWaitForSolutions ) : m_errorState( ErrorState( NoError ) ) , m_queriesShouldWaitForSolutions( queriesShouldWaitForSolutions ) { DEBUG_BLOCK } Controller::~Controller() { DEBUG_BLOCK } void Controller::resolve( const QString &artist, const QString &album, const QString &title ) { DEBUG_BLOCK debug() << "Querying playdar for artist name = " << artist << ", album name = " << album << ", and track title = " << title; const QString baseUrl( "http://localhost:60210/api/?method=resolve" ); - KUrl resolveUrl( baseUrl ); + QUrl resolveUrl( baseUrl ); resolveUrl.addQueryItem( QString( "artist" ), artist ); resolveUrl.addQueryItem( QString( "album" ), album ); resolveUrl.addQueryItem( QString( "track" ), title ); debug() << "Starting storedGetJob for " << resolveUrl.url(); KJob* resolveJob = KIO::storedGet( resolveUrl, KIO::Reload, KIO::HideProgressInfo ); connect( resolveJob, SIGNAL(result(KJob*)), this, SLOT(processQuery(KJob*)) ); } void Controller::getResults( Query* query ) { DEBUG_BLOCK const QString baseUrl( "http://localhost:60210/api/?method=get_results" ); - KUrl getResultsUrl( baseUrl ); + QUrl getResultsUrl( baseUrl ); getResultsUrl.addQueryItem( QString( "qid" ), query->qid() ); KJob* getResultsJob = KIO::storedGet( getResultsUrl, KIO::Reload, KIO::HideProgressInfo ); connect( getResultsJob, SIGNAL(result(KJob*)), query, SLOT(receiveResults(KJob*)) ); } void Controller::getResultsLongPoll( Query* query ) { DEBUG_BLOCK const QString baseUrl( "http://localhost:60210/api/?method=get_results_long" ); - KUrl getResultsUrl( baseUrl ); + QUrl getResultsUrl( baseUrl ); getResultsUrl.addQueryItem( QString( "qid" ), query->qid() ); KJob* getResultsJob = KIO::storedGet( getResultsUrl, KIO::Reload, KIO::HideProgressInfo ); connect( getResultsJob, SIGNAL(result(KJob*)), query, SLOT(receiveResults(KJob*)) ); } - KUrl + QUrl Controller::urlForSid( const QString &sid ) const { DEBUG_BLOCK const QString baseUrl( "http://localhost:60210/sid/" ); - KUrl playableUrl( baseUrl ); + QUrl playableUrl( baseUrl ); - playableUrl.addPath( sid ); + playableUrl = playableUrl.adjusted(QUrl::StripTrailingSlash); + playableUrl.setPath(playableUrl.path() + '/' + ( sid )); return playableUrl; } void Controller::status() { // DEBUG_BLOCK const QString baseUrl( "http://localhost:60210/api/?method=stat" ); - KUrl statusUrl( baseUrl ); + QUrl statusUrl( baseUrl ); KJob* statusJob = KIO::storedGet( statusUrl, KIO::Reload, KIO::HideProgressInfo ); connect( statusJob, SIGNAL(result(KJob*)), this, SLOT(processStatus(KJob*)) ); } void Controller::processStatus( KJob *statusJob ) { if( statusJob->error() != 0 ) { // debug() << "Error getting status from Playdar"; emit playdarError( Playdar::Controller::ErrorState( ExternalError ) ); return; } debug() << "Processing received JSON data..."; KIO::StoredTransferJob* storedStatusJob = static_cast( statusJob ); QVariant parsedStatusVariant; QJson::Parser parser; bool ok; parsedStatusVariant = parser.parse( storedStatusJob->data(),&ok ); if ( !ok ) { debug() << "Error parsing JSON Data"; } QVariantMap parsedStatus = parsedStatusVariant.toMap(); if( !parsedStatus.contains("name") ) { debug() << "Expected a service name from Playdar, received none"; emit playdarError( Playdar::Controller::ErrorState( MissingServiceName ) ); return; } if( parsedStatus.value("name") != QString( "playdar" ) ) { debug() << "Expected Playdar, got response from some other service"; emit playdarError( Playdar::Controller::ErrorState( WrongServiceName ) ); return; } debug() << "All good! Emitting playdarReady()"; emit playdarReady(); } void Controller::processQuery( KJob *queryJob ) { DEBUG_BLOCK if( queryJob->error() != 0 ) { debug() << "Error getting qid from Playdar"; emit playdarError( Playdar::Controller::ErrorState( ExternalError ) ); return; } debug() << "Processing received JSON data..."; KIO::StoredTransferJob* storedQueryJob = static_cast( queryJob ); QVariant parsedQueryVariant; QJson::Parser parser; bool ok; parsedQueryVariant = parser.parse( storedQueryJob->data(),&ok ); if ( !ok ) { debug() << "Error parsing JSON Data"; } QVariantMap parsedQuery = parsedQueryVariant.toMap(); if( !parsedQuery.contains( "qid" ) ) { debug() << "Expected qid in Playdar's response, but didn't get it"; emit playdarError( Playdar::Controller::ErrorState( MissingQid ) ); return; } Query* query = new Query( parsedQuery.value( "qid" ).toString(), this, m_queriesShouldWaitForSolutions ); debug() << "All good! Emitting queryReady( Playdar::Query* )..."; emit queryReady( query ); connect( query, SIGNAL(playdarError(Playdar::Controller::ErrorState)), this, SIGNAL(playdarError(Playdar::Controller::ErrorState)) ); } } diff --git a/src/core-impl/collections/playdarcollection/support/Controller.h b/src/core-impl/collections/playdarcollection/support/Controller.h index c6ce64df70..1e94e29733 100644 --- a/src/core-impl/collections/playdarcollection/support/Controller.h +++ b/src/core-impl/collections/playdarcollection/support/Controller.h @@ -1,158 +1,158 @@ /**************************************************************************************** * Copyright (c) 2010 Andrew Coder * * * * 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, see . * ****************************************************************************************/ #ifndef PLAYDAR_CONTROLLER_H #define PLAYDAR_CONTROLLER_H #include #include -class KUrl; +class QUrl; class KJob; class QString; /** * @namespace Playdar contains the implementation of the Playdar API * in Amarok, so far as it is not specific to collection or context use. */ namespace Playdar { class Query; class ProxyResolver; /** * This class provides a basic interface to Playdar's resolution * functionality. A user should initialize a Controller, wait for * playdarReady(), and proceed to use resolve( artist, album, title ) * as much as they'd like. Unless some error occurs, queryReady( QueryPtr ) * will provide a QueryPtr for each call to resolve(). Results will * be provided by the appropriate Query as they become available, and * the friendly relationship between Controller and Query ensures that * results are properly matched with Queries. */ class Controller : public QObject { Q_OBJECT public: /** * We invoke the private function status() here, return immediately, * and will emit playdarReady() once things are actually set up. * @param queriesShouldWaitForSolutions * If true, Playdar::Queries created by this controller will * only use getResultsLongPoll instead of first using getResults. */ Controller( bool queriesShouldWaitForSolutions = false ); /** * Controllers don't hold on to anything, so the deconstructor does nothing. */ ~Controller(); /** * Asks Playdar for status information, which eventually results in * the emission of playdarReady() if it does or error() otherwise. */ void status(); /** * Asks Playdar to resolve a query, which eventually results in the * emission of queryReady() if okay, or error() if something goes wrong. * @param artist Name of artist to search for. * @param album Name of album (by artist) to search for. * @param title Name of track on album to search for. * NOTE: Currently, for a query to have any chance of being solved, * both artist and title must be non-empty! */ void resolve( const QString &artist, const QString &album, const QString &title ); /** These errors are used when Playdar::Controller emits error(). */ enum ErrorState { /** Nothing bad happened yet! */ NoError, /** Indicates an error from KIO or the Playdar service itself. */ ExternalError, /** A request for status revealed a service claiming to not be Playdar. */ WrongServiceName, /** A service asked for status didn't identify itself. */ MissingServiceName, /** No "qid" field was found in a response to resolve() or getResults(). */ MissingQid, /** Results were delivered to the wrong query */ WrongQid, /** A response to getResults() didn't include a "results" array. */ MissingResults }; signals: /** * Emitted once after construction, as long as some service responds * from where we think Playdar is, and identifies itself as "playdar". * Clients shouldn't try to resolve things until they get this signal. */ void playdarReady(); /** * Emitted after resolve() is invoked, as long as Playdar's response * includes a "qid" field. If the client object's looking for results, * the Query's where they'll be when Playdar's got back to us. The * controller doesn't keep any QueryPtrs around, so interested clients * should keep a QueryList for any QueryPtrs they'd like to have. */ void queryReady( Playdar::Query* ); /** * Emitted after something unfortunate happens, * along with the (hopefully) appropriate ErrorType */ void playdarError( Playdar::Controller::ErrorState ); public: /** * NOTE: Queries handle invoking these on their own. */ /** * Asks Playdar for the state of the query with @p qid. * If all goes well, resultsReady() is emitted, error() if something goes wrong. * @param query The query to get results for */ void getResults( Playdar::Query* query ); /** * Like getResults(), but Playdar will wait to respond until the query * has either been solved or enough time passes, (usually about 4000ms), * that the query would have been solved it was possible. * @param query The query to get results for */ void getResultsLongPoll( Playdar::Query* query ); /** - * Makes a naive attempt to produce a KUrl that points to + * Makes a naive attempt to produce a QUrl that points to * a playable location of a query result. * @param sid The sid of the result desired. */ - KUrl urlForSid( const QString &sid ) const; + QUrl urlForSid( const QString &sid ) const; private Q_SLOTS: void processStatus( KJob* statusJob ); void processQuery( KJob* queryJob ); private: ErrorState m_errorState; bool m_queriesShouldWaitForSolutions; }; } #endif diff --git a/src/core-impl/collections/playdarcollection/support/ProxyResolver.cpp b/src/core-impl/collections/playdarcollection/support/ProxyResolver.cpp index c7683c1abb..c8c2850a9f 100644 --- a/src/core-impl/collections/playdarcollection/support/ProxyResolver.cpp +++ b/src/core-impl/collections/playdarcollection/support/ProxyResolver.cpp @@ -1,93 +1,93 @@ /**************************************************************************************** * Copyright (c) 2010 Andrew Coder * * * * 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, see . * ****************************************************************************************/ #include "ProxyResolver.h" #include "Controller.h" #include "../PlaydarCollection.h" #include "../PlaydarMeta.h" #include "core-impl/meta/proxy/MetaProxy.h" -#include +#include #include Playdar::ProxyResolver::ProxyResolver( Collections::PlaydarCollection *collection, - const KUrl &url, MetaProxy::TrackPtr track ) + const QUrl &url, MetaProxy::TrackPtr track ) : m_collection( collection ) , m_proxyTrack( track ) , m_controller( new Playdar::Controller( true ) ) , m_query() { connect( m_controller, SIGNAL(playdarError(Playdar::Controller::ErrorState)), this, SLOT(slotPlaydarError(Playdar::Controller::ErrorState)) ); connect( m_controller, SIGNAL(queryReady(Playdar::Query*)), this, SLOT(collectQuery(Playdar::Query*)) ); - m_controller->resolve( url.queryItem( "artist" ), - url.queryItem( "album" ), - url.queryItem( "title" ) ); + m_controller->resolve( QUrlQuery(url).queryItemValue( "artist" ), + QUrlQuery(url).queryItemValue( "album" ), + QUrlQuery(url).queryItemValue( "title" ) ); } Playdar::ProxyResolver::~ProxyResolver() { delete m_query; delete m_controller; } void Playdar::ProxyResolver::slotPlaydarError( Playdar::Controller::ErrorState error ) { emit playdarError( error ); this->deleteLater(); } void Playdar::ProxyResolver::collectQuery( Playdar::Query *query ) { m_query = query; connect( m_query, SIGNAL(querySolved(Meta::PlaydarTrackPtr)), this, SLOT(collectSolution(Meta::PlaydarTrackPtr)) ); connect( m_query, SIGNAL(queryDone(Playdar::Query*,Meta::PlaydarTrackList)), this, SLOT(slotQueryDone(Playdar::Query*,Meta::PlaydarTrackList)) ); } void Playdar::ProxyResolver::collectSolution( Meta::PlaydarTrackPtr track ) { if( !m_proxyTrack->isPlayable() ) { Meta::TrackPtr realTrack; if( !m_collection.isNull() ) { track->addToCollection( m_collection ); realTrack = m_collection->trackForUrl( track->uidUrl() ); } else realTrack = Meta::TrackPtr::staticCast( track ); m_proxyTrack->updateTrack( realTrack ); } } void Playdar::ProxyResolver::slotQueryDone( Playdar::Query* query, const Meta::PlaydarTrackList& tracks ) { Q_UNUSED( query ); Q_UNUSED( tracks ); this->deleteLater(); } diff --git a/src/core-impl/collections/playdarcollection/support/ProxyResolver.h b/src/core-impl/collections/playdarcollection/support/ProxyResolver.h index 06a066913c..503d179abf 100644 --- a/src/core-impl/collections/playdarcollection/support/ProxyResolver.h +++ b/src/core-impl/collections/playdarcollection/support/ProxyResolver.h @@ -1,61 +1,61 @@ /**************************************************************************************** * Copyright (c) 2010 Andrew Coder * * * * 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, see . * ****************************************************************************************/ #ifndef PLAYDAR_PROXY_RESOLVER_H #define PLAYDAR_PROXY_RESOLVER_H #include "Controller.h" #include "../PlaydarCollection.h" #include "../PlaydarMeta.h" #include "core-impl/meta/proxy/MetaProxy.h" #include -class KUrl; +class QUrl; namespace Playdar { /** * ProxyResolver takes a MetaProxy::Track and a playdar:// url, * and updates the Track with a PlaydarTrack if we can find it. */ class ProxyResolver : public QObject { Q_OBJECT public: ProxyResolver( Collections::PlaydarCollection *collection, - const KUrl &url, MetaProxy::TrackPtr track ); + const QUrl &url, MetaProxy::TrackPtr track ); ~ProxyResolver(); signals: void playdarError( Playdar::Controller::ErrorState ); private Q_SLOTS: void slotPlaydarError( Playdar::Controller::ErrorState error ); void collectQuery( Playdar::Query *query ); void collectSolution( Meta::PlaydarTrackPtr track ); void slotQueryDone( Playdar::Query *query, const Meta::PlaydarTrackList & tracks ); private: QPointer< Collections::PlaydarCollection > m_collection; MetaProxy::TrackPtr m_proxyTrack; Playdar::Controller* m_controller; Playdar::Query* m_query; }; } #endif diff --git a/src/core-impl/collections/playdarcollection/support/Query.cpp b/src/core-impl/collections/playdarcollection/support/Query.cpp index 327d72951b..5a2800470c 100644 --- a/src/core-impl/collections/playdarcollection/support/Query.cpp +++ b/src/core-impl/collections/playdarcollection/support/Query.cpp @@ -1,219 +1,219 @@ /**************************************************************************************** * Copyright (c) 2010 Andrew Coder * * * 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, see . * ****************************************************************************************/ #include "Query.h" #include "Controller.h" #include "core/meta/Meta.h" #include "../PlaydarMeta.h" #include "core/support/Debug.h" #include -#include +#include #include #include #include #include #include namespace Playdar { Query::Query( const QString &qid, Playdar::Controller* controller, bool waitForSolution ) : m_controller( controller ) , m_waitForSolution( waitForSolution ) , m_qid( qid ) , m_artist( QString( "" ) ) , m_album( QString( "" ) ) , m_title( QString( "" ) ) , m_solved( false ) , m_receivedFirstResults( false ) , m_trackList( ) { DEBUG_BLOCK if( m_waitForSolution ) { m_receivedFirstResults = true; m_controller.data()->getResultsLongPoll( this ); } else m_controller.data()->getResults( this ); } Query::~Query() { DEBUG_BLOCK } QString Query::qid() const { DEBUG_BLOCK return m_qid; } QString Query::artist() const { DEBUG_BLOCK return m_artist; } QString Query::album() const { DEBUG_BLOCK return m_album; } QString Query::title() const { DEBUG_BLOCK return m_title; } bool Query::isSolved() const { DEBUG_BLOCK return m_solved; } Meta::PlaydarTrackList Query::getTrackList() const { DEBUG_BLOCK return m_trackList; } void Query::receiveResults( KJob* resultsJob) { DEBUG_BLOCK if( resultsJob->error() != 0 ) { debug() << "Error getting results from Playdar"; emit playdarError( Playdar::Controller::ErrorState( 1 ) ); return; } debug() << "Processing received JSON data..."; KIO::StoredTransferJob* storedResultsJob = static_cast( resultsJob ); QJson::Parser parser; bool ok; QVariant parsedResultsVariant; parsedResultsVariant = parser.parse( storedResultsJob->data(),&ok ); if ( !ok ) { debug() << "Error parsing JSON Data"; } QVariantMap parsedResults = parsedResultsVariant.toMap(); if( !parsedResults.contains( "results" ) ) { debug() << "Expecting results in Playdar's response, received none"; emit playdarError( Playdar::Controller::ErrorState( 6 ) ); return; } if( !parsedResults.contains( "qid" ) ) { debug() << "Expected qid in Playdar's response, received none"; emit playdarError( Playdar::Controller::ErrorState( 4 ) ); return; } if( parsedResults.value( "qid" ) != m_qid ) { debug() << "A query received the wrong results from Playdar..."; emit playdarError( Playdar::Controller::ErrorState( 5 ) ); return; } m_artist = parsedResults.value( "artist" ).toString(); m_album = parsedResults.value( "album" ).toString(); m_title = parsedResults.value( "track" ).toString(); foreach( const QVariant &resultVariant, parsedResults.value( "results" ).toList() ) { QVariantMap result = resultVariant.toMap(); Meta::PlaydarTrackPtr aTrack; - KUrl resultUrl( m_controller.data()->urlForSid( result.value( "sid" ).toString() ) ); + QUrl resultUrl( m_controller.data()->urlForSid( result.value( "sid" ).toString() ) ); QString trackSid = result.value( "sid" ).toString(); QString trackUrl = resultUrl.url(); QString trackTitle = result.value( "track" ).toString(); QString trackArtist = result.value( "artist" ).toString(); QString trackAlbum = result.value( "album" ).toString(); QString trackType = result.value( "mimetype" ).toString(); QString trackSource = result.value( "source" ).toString(); qint64 trackLengthInSeconds( result.value( "duration" ).toInt() ); aTrack = new Meta::PlaydarTrack ( trackSid, trackUrl, trackTitle, trackArtist, trackAlbum, trackType, result.value( "score" ).toDouble() * 100, ( trackLengthInSeconds * 1000 ), //convert s to ms result.value( "bitrate" ).toInt(), result.value( "size" ).toInt(), trackSource ); if( !m_solved && aTrack->score() >= 1.00 ) { m_solved = true; m_trackList.prepend( aTrack ); emit querySolved( aTrack ); if( m_waitForSolution ) { emit queryDone( this, m_trackList ); return; } } else { m_trackList.append( aTrack ); } emit newTrackAdded( aTrack ); } if( m_receivedFirstResults || m_solved ) { m_receivedFirstResults = true; emit queryDone( this, m_trackList ); } else { m_receivedFirstResults = true; m_controller.data()->getResultsLongPoll( this ); } } } diff --git a/src/core-impl/collections/support/CollectionManager.cpp b/src/core-impl/collections/support/CollectionManager.cpp index 1cb46c7cef..76aa69a197 100644 --- a/src/core-impl/collections/support/CollectionManager.cpp +++ b/src/core-impl/collections/support/CollectionManager.cpp @@ -1,426 +1,426 @@ /**************************************************************************************** * Copyright (c) 2007-2008 Maximilian Kossick * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "CollectionManager" #include "CollectionManager.h" #include "core/capabilities/CollectionScanCapability.h" #include "core/collections/Collection.h" #include "core/collections/MetaQueryMaker.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include "core/support/SmartPointerList.h" #include "core-impl/meta/file/FileTrackProvider.h" #include "core-impl/meta/stream/Stream.h" #include "core-impl/meta/timecode/TimecodeTrackProvider.h" #include #include #include #include #include #include #include typedef QPair CollectionPair; /** Private structure of the collection manager */ struct CollectionManager::Private { QList collections; QList factories; // factories belong to PluginManager QList trackProviders; TimecodeTrackProvider *timecodeTrackProvider; Collections::TrackProvider *fileTrackProvider; // special case Collections::Collection *primaryCollection; QReadWriteLock lock; ///< protects all other variables against threading issues }; CollectionManager *CollectionManager::s_instance = 0; CollectionManager * CollectionManager::instance() { if( !s_instance ) { s_instance = new CollectionManager(); s_instance->init(); } return s_instance; } void CollectionManager::destroy() { if( s_instance ) { delete s_instance; s_instance = 0; } } CollectionManager::CollectionManager() : QObject() , d( new Private ) { DEBUG_BLOCK // ensure this object is created in a main thread Q_ASSERT( thread() == QCoreApplication::instance()->thread() ); setObjectName( "CollectionManager" ); d->primaryCollection = 0; d->timecodeTrackProvider = 0; d->fileTrackProvider = 0; } CollectionManager::~CollectionManager() { DEBUG_BLOCK { QWriteLocker locker( &d->lock ); d->collections.clear(); d->trackProviders.clear(); delete d->timecodeTrackProvider; delete d->fileTrackProvider; // Hmm, qDeleteAll from Qt 4.8 crashes with our SmartPointerList, do it manually. Bug 285951 while (!d->factories.isEmpty() ) delete d->factories.takeFirst(); } delete d; } void CollectionManager::init() { // register the timecode track provider now, as it needs to get added before loading // the stored playlist... Since it can have playable urls that might also match other providers, it needs to get added first. d->timecodeTrackProvider = new TimecodeTrackProvider(); addTrackProvider( d->timecodeTrackProvider ); // addint fileTrackProvider second since local tracks should be preferred even if the url matchs two tracks d->fileTrackProvider = new FileTrackProvider(); addTrackProvider( d->fileTrackProvider ); } void CollectionManager::setFactories( const QList &factories ) { using Collections::CollectionFactory; QSet newFactories = factories.toSet(); QSet oldFactories; { QReadLocker locker( &d->lock ); oldFactories = d->factories.toSet(); } // remove old factories foreach( Plugins::PluginFactory* pFactory, oldFactories - newFactories ) { CollectionFactory *factory = qobject_cast( pFactory ); if( !factory ) continue; disconnect( factory, SIGNAL(newCollection(Collections::Collection*)), this, SLOT(slotNewCollection(Collections::Collection*)) ); { QWriteLocker locker( &d->lock ); d->factories.removeAll( factory ); } } // create new factories foreach( Plugins::PluginFactory* pFactory, newFactories - oldFactories ) { CollectionFactory *factory = qobject_cast( pFactory ); if( !factory ) continue; connect( factory, SIGNAL(newCollection(Collections::Collection*)), this, SLOT(slotNewCollection(Collections::Collection*)) ); { QWriteLocker locker( &d->lock ); d->factories.append( factory ); } } d->factories = factories; } void CollectionManager::startFullScan() { QReadLocker locker( &d->lock ); foreach( const CollectionPair &pair, d->collections ) { QScopedPointer csc( pair.first->create()); if( csc ) csc->startFullScan(); } } void CollectionManager::startIncrementalScan( const QString &directory ) { QReadLocker locker( &d->lock ); foreach( const CollectionPair &pair, d->collections ) { QScopedPointer csc( pair.first->create()); if( csc ) csc->startIncrementalScan( directory ); } } void CollectionManager::stopScan() { QReadLocker locker( &d->lock ); foreach( const CollectionPair &pair, d->collections ) { QScopedPointer csc( pair.first->create()); if( csc ) csc->stopScan(); } } void CollectionManager::checkCollectionChanges() { startIncrementalScan( QString() ); } Collections::QueryMaker* CollectionManager::queryMaker() const { QReadLocker locker( &d->lock ); QList colls; foreach( const CollectionPair &pair, d->collections ) { if( pair.second & CollectionQueryable ) { colls << pair.first; } } return new Collections::MetaQueryMaker( colls ); } void CollectionManager::slotNewCollection( Collections::Collection* newCollection ) { DEBUG_BLOCK if( !newCollection ) { error() << "newCollection in slotNewCollection is 0"; return; } { QWriteLocker locker( &d->lock ); foreach( const CollectionPair &p, d->collections ) { if( p.first == newCollection ) { error() << "newCollection " << newCollection->collectionId() << " is already being managed"; return; } } } const QMetaObject *mo = metaObject(); const QMetaEnum me = mo->enumerator( mo->indexOfEnumerator( "CollectionStatus" ) ); const QString &value = KGlobal::config()->group( "CollectionManager" ).readEntry( newCollection->collectionId() ); int enumValue = me.keyToValue( value.toLocal8Bit().constData() ); CollectionStatus status; enumValue == -1 ? status = CollectionEnabled : status = (CollectionStatus) enumValue; CollectionPair pair( newCollection, status ); { QWriteLocker locker( &d->lock ); if( newCollection->collectionId() == QLatin1String("localCollection") ) { d->primaryCollection = newCollection; d->collections.insert( 0, pair ); // the primary collection should be the first collection to be searched d->trackProviders.insert( 2, newCollection ); // the primary collection should be between the timecode track provider and the local file track provider } else { d->collections.append( pair ); d->trackProviders.append( newCollection ); } connect( newCollection, SIGNAL(remove()), SLOT(slotRemoveCollection()), Qt::QueuedConnection ); connect( newCollection, SIGNAL(updated()), SLOT(slotCollectionChanged()), Qt::QueuedConnection ); debug() << "new Collection " << newCollection->collectionId(); } if( status & CollectionViewable ) { emit collectionAdded( newCollection ); emit collectionAdded( newCollection, status ); } } void CollectionManager::slotRemoveCollection() { Collections::Collection* collection = qobject_cast( sender() ); if( collection ) { CollectionStatus status = collectionStatus( collection->collectionId() ); CollectionPair pair( collection, status ); { QWriteLocker locker( &d->lock ); d->collections.removeAll( pair ); d->trackProviders.removeAll( collection ); } emit collectionRemoved( collection->collectionId() ); QTimer::singleShot( 500, collection, SLOT(deleteLater()) ); // give the tree some time to update itself until we really delete the collection pointers. } } void CollectionManager::slotCollectionChanged() { Collections::Collection *collection = dynamic_cast( sender() ); if( collection ) { CollectionStatus status = collectionStatus( collection->collectionId() ); if( status & CollectionViewable ) { emit collectionDataChanged( collection ); } } } QList CollectionManager::viewableCollections() const { QReadLocker locker( &d->lock ); QList result; foreach( const CollectionPair &pair, d->collections ) { if( pair.second & CollectionViewable ) { result << pair.first; } } return result; } Collections::Collection* CollectionManager::primaryCollection() const { QReadLocker locker( &d->lock ); return d->primaryCollection; } Meta::TrackPtr -CollectionManager::trackForUrl( const KUrl &url ) +CollectionManager::trackForUrl( const QUrl &url ) { QReadLocker locker( &d->lock ); // TODO: // might be a podcast, in that case we'll have additional meta information // might be a lastfm track, another stream if( !url.isValid() ) return Meta::TrackPtr( 0 ); foreach( Collections::TrackProvider *provider, d->trackProviders ) { if( provider->possiblyContainsTrack( url ) ) { Meta::TrackPtr track = provider->trackForUrl( url ); if( track ) return track; } } // TODO: create specific TrackProviders for these: static const QSet remoteProtocols = QSet() << "http" << "https" << "mms" << "smb"; // consider unifying with TrackLoader::tracksLoaded() - if( remoteProtocols.contains( url.protocol() ) ) + if( remoteProtocols.contains( url.scheme() ) ) return Meta::TrackPtr( new MetaStream::Track( url ) ); return Meta::TrackPtr( 0 ); } CollectionManager::CollectionStatus CollectionManager::collectionStatus( const QString &collectionId ) const { QReadLocker locker( &d->lock ); foreach( const CollectionPair &pair, d->collections ) { if( pair.first->collectionId() == collectionId ) { return pair.second; } } return CollectionDisabled; } QHash CollectionManager::collections() const { QReadLocker locker( &d->lock ); QHash result; foreach( const CollectionPair &pair, d->collections ) { result.insert( pair.first, pair.second ); } return result; } void CollectionManager::addTrackProvider( Collections::TrackProvider *provider ) { { QWriteLocker locker( &d->lock ); d->trackProviders.append( provider ); } emit trackProviderAdded( provider ); } void CollectionManager::removeTrackProvider( Collections::TrackProvider *provider ) { QWriteLocker locker( &d->lock ); d->trackProviders.removeAll( provider ); } diff --git a/src/core-impl/collections/support/CollectionManager.h b/src/core-impl/collections/support/CollectionManager.h index 5f56394d8f..5b196f0f71 100644 --- a/src/core-impl/collections/support/CollectionManager.h +++ b/src/core-impl/collections/support/CollectionManager.h @@ -1,178 +1,178 @@ /**************************************************************************************** * Copyright (c) 2007-2008 Maximilian Kossick * * * * 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, see . * ****************************************************************************************/ #ifndef AMAROK_COLLECTIONMANAGER_H #define AMAROK_COLLECTIONMANAGER_H #include "amarok_export.h" #include "core/meta/forward_declarations.h" -#include +#include #include #include class CollectionManagerSingleton; namespace Plugins { class PluginFactory; } namespace Collections { class Collection; class CollectionFactory; class TrackProvider; class QueryMaker; } /** Class managing the different collections. * * This singleton class is the main repository for all current collections. * The most usefull functions are probably queryMaker and * viewableCollections */ class AMAROK_EXPORT CollectionManager : public QObject { Q_OBJECT Q_ENUMS( CollectionStatus ) public: /** * defines the status of a collection in respect to global queries (i.e. queries that query all known collections) * or the collection browser. */ enum CollectionStatus { CollectionDisabled = 1, ///< Collection neither viewable nor queryable (but might produce tracks that can be played) CollectionViewable = 2, ///< Collection will not be queried by CollectionManager::queryMaker CollectionQueryable= 4, ///< Collection wil not show up in the browser, but is queryable by global queries CollectionEnabled = CollectionViewable | CollectionQueryable ///< Collection viewable in the browser and queryable }; static CollectionManager *instance(); /** Destroys the instance of the CollectionManager. */ static void destroy(); /** * Returns a query maker that queries all queryable the collections */ Collections::QueryMaker *queryMaker() const; /** * returns all viewable collections. */ QList viewableCollections() const; //TODO: Remove /** * Allows access to one of Amarok's collection. * * @deprecated DO NOT USE this method. This is legacy code from the early days of Amarok * when SqlCollection was the only working collection. If you are writing new code, make * sure that it is able to handle multiple collections, * e.g. multiple connected media devices. Using this method means you are lazy. An easy way * is to use CollectionManager::queryMaker(). Alternatively, access the available collections * using CollectionManager::queryableCollections() or CollectionManager::viewableCollections() * and do whatever you want to. * */ Collections::Collection* primaryCollection() const; /** This method will try to get a Track object for the given url. This method will return 0 if no Track object could be created for the url. */ - Meta::TrackPtr trackForUrl( const KUrl &url ); + Meta::TrackPtr trackForUrl( const QUrl &url ); CollectionStatus collectionStatus( const QString &collectionId ) const; QHash collections() const; /** * adds a TrackProvider to the list of TrackProviders, * which allows CollectionManager to create tracks in trackForUrl. * CollectionManager does not take ownership of the TrackProvider pointer * * Note: collections that CollectionManager knows about are automatically * added to the list of TrackProviders. * * @param provider the new TrackProvider */ void addTrackProvider( Collections::TrackProvider *provider ); /** * removes a TrackProvider. Does not do anything if * CollectionManager does not know the given TrackProvider. * * Note: collections will be automatically removed from * the list of available TrackProviders. * * @param provider the provider to be removed */ void removeTrackProvider( Collections::TrackProvider *provider ); /** * Set the list of current factories * * For every factory that is a CollectionFactory uses it to create new * collections and register with this manager. */ void setFactories( const QList &factories ); public slots: /** Starts the full scan for each collection with CollectionScanCapability */ void startFullScan(); /** Starts the incremetal scan for each collection with CollectionScanCapability */ void startIncrementalScan( const QString &directory = QString() ); void stopScan(); void checkCollectionChanges(); signals: //deprecated, use collectionAdded( Collections::Collection*, CollectionStatus ) instead void collectionAdded( Collections::Collection *newCollection ); void collectionAdded( Collections::Collection *newCollection, CollectionManager::CollectionStatus status ); void collectionRemoved( QString collectionId ); void trackProviderAdded( Collections::TrackProvider *provider ); //this signal will be emitted after major changes to the collection //e.g. new songs where added, or an album changed //from compilation to non-compilation (and vice versa) //it will not be emitted on minor changes (e.g. the tags of a song were changed) void collectionDataChanged( Collections::Collection *changedCollection ); private slots: /** Will be called whenever a registered collection factory creates a new collection */ void slotNewCollection( Collections::Collection *newCollection ); /** Will remove the collection that emitted the signal */ void slotRemoveCollection(); void slotCollectionChanged(); private: static CollectionManager* s_instance; CollectionManager(); ~CollectionManager(); void init(); Q_DISABLE_COPY( CollectionManager ) struct Private; Private * const d; }; #endif /* AMAROK_COLLECTIONMANAGER_H */ diff --git a/src/core-impl/collections/support/FileCollectionLocation.cpp b/src/core-impl/collections/support/FileCollectionLocation.cpp index acc86b4720..129f60a515 100644 --- a/src/core-impl/collections/support/FileCollectionLocation.cpp +++ b/src/core-impl/collections/support/FileCollectionLocation.cpp @@ -1,134 +1,134 @@ /**************************************************************************************** * Copyright (c) 2008 Casey Link * * Copyright (c) 2008 Jason A. Donenfeld * * * * 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, see . * ****************************************************************************************/ #include "FileCollectionLocation.h" #include "core/collections/CollectionLocationDelegate.h" #include "core/interfaces/Logger.h" #include "core/support/Components.h" #include "core/support/Debug.h" #include #include #include #include #include #include #include using namespace Collections; FileCollectionLocation::FileCollectionLocation() : CollectionLocation() { // nothing to do } FileCollectionLocation::~FileCollectionLocation() { // nothing to do } QString FileCollectionLocation::prettyLocation() const { return "File Browser Location"; } bool FileCollectionLocation::isWritable() const { return true; } bool FileCollectionLocation::isOrganizable() const { return false; } void FileCollectionLocation::startRemoveJobs() { DEBUG_BLOCK while ( !m_removetracks.isEmpty() ) { Meta::TrackPtr track = m_removetracks.takeFirst(); - KUrl src = track->playableUrl(); + QUrl src = track->playableUrl(); KIO::DeleteJob *job = 0; src.cleanPath(); debug() << "deleting " << src; KIO::JobFlags flags = KIO::HideProgressInfo; job = KIO::del( src, flags ); connect( job, SIGNAL(result(KJob*)), SLOT(slotRemoveJobFinished(KJob*)) ); QString name = track->prettyName(); if( track->artist() ) name = QString( "%1 - %2" ).arg( track->artist()->name(), track->prettyName() ); Amarok::Components::logger()->newProgressOperation( job, i18n( "Removing: %1", name ) ); m_removejobs.insert( job, track ); } } void FileCollectionLocation::slotRemoveJobFinished( KJob *job ) { // ignore and error that the file did not exist in the first place. Perhaps destination // collection too eager? :-) if( job->error() && job->error() != KIO::ERR_DOES_NOT_EXIST ) transferError( m_removejobs.value( job ), KIO::buildErrorString( job->error(), job->errorString() ) ); else transferSuccessful( m_removejobs.value( job ) ); m_removejobs.remove( job ); job->deleteLater(); if(m_removejobs.isEmpty()) { slotRemoveOperationFinished(); } } void FileCollectionLocation::removeUrlsFromCollection(const Meta::TrackList& sources) { DEBUG_BLOCK m_removetracks = sources; debug() << "removing " << m_removetracks.size() << "tracks"; startRemoveJobs(); } void FileCollectionLocation::showRemoveDialog( const Meta::TrackList &tracks ) { DEBUG_BLOCK if( !isHidingRemoveConfirm() ) { Collections::CollectionLocationDelegate *delegate = Amarok::Components::collectionLocationDelegate(); const bool del = delegate->reallyDelete( this, tracks ); if( !del ) abort(); else slotShowRemoveDialogDone(); } else slotShowRemoveDialogDone(); } diff --git a/src/core-impl/collections/support/MemoryMeta.h b/src/core-impl/collections/support/MemoryMeta.h index 93a4bfffcc..46baec7f86 100644 --- a/src/core-impl/collections/support/MemoryMeta.h +++ b/src/core-impl/collections/support/MemoryMeta.h @@ -1,357 +1,357 @@ /**************************************************************************************** * Copyright (c) 2011 Bart Cerneels * * * * 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, see . * ****************************************************************************************/ #ifndef MEMORYMETA_H #define MEMORYMETA_H #include "amarok_export.h" #include "MemoryCollection.h" #include "core/meta/Meta.h" using namespace Collections; /** These classes can be used with a MemoryCollection to populate the meta-type maps */ namespace MemoryMeta { class Track; /** * Base class for all MemoryMeta:: entities that store a list of associated tracks: * Artist, Album, Composer, Genre, Year. * * All methods of this class are thread-safe. */ class Base { public: Base( const QString &name ) : m_name( name ) {} virtual ~Base() {} // Meta::{Artist,Album,Composer,Genre,Year} methods: virtual QString name() const { return m_name; } virtual Meta::TrackList tracks(); // MemoryMeta::Base methods: void addTrack( Track *track ); void removeTrack( Track *track ); private: QString m_name; /* We cannot easily store KSharedPtr to tracks, because it creates reference * counting cycle: MemoryMeta::Track::m_album -> MemoryMeta::Album::tracks() -> * MemoryMeta::Track. We therefore store plain pointers and rely on * MemoryMeta::Track to notify when it is destroyed. */ QList m_tracks; QReadWriteLock m_tracksLock; }; class Artist : public Meta::Artist, public Base { public: Artist( const QString &name ) : MemoryMeta::Base( name ) {} virtual QString name() const { return MemoryMeta::Base::name(); } virtual Meta::TrackList tracks() { return MemoryMeta::Base::tracks(); } }; class Album : public Meta::Album, public Base { public: Album( const QString &name, const Meta::ArtistPtr &albumArtist ) : MemoryMeta::Base( name ) , m_albumArtist( albumArtist ) , m_isCompilation( false ) , m_canUpdateCompilation( false ) , m_canUpdateImage( false ) {} /** * Copy-like constructor for MapChanger */ Album( const Meta::AlbumPtr &other ); /* Meta::MetaCapability virtual methods */ virtual bool hasCapabilityInterface( Capabilities::Capability::Type type ) const; virtual Capabilities::Capability* createCapabilityInterface( Capabilities::Capability::Type type ); /* Meta::Base virtual methods */ virtual QString name() const { return MemoryMeta::Base::name(); } /* Meta::Album virtual methods */ virtual bool isCompilation() const { return m_isCompilation; } virtual bool canUpdateCompilation() const { return m_canUpdateCompilation; } virtual void setCompilation( bool isCompilation ); virtual bool hasAlbumArtist() const { return !m_albumArtist.isNull(); } virtual Meta::ArtistPtr albumArtist() const { return m_albumArtist; } virtual Meta::TrackList tracks() { return MemoryMeta::Base::tracks(); } virtual bool hasImage( int /* size */ = 0 ) const { return !m_image.isNull(); } virtual QImage image( int size = 0 ) const; virtual bool canUpdateImage() const { return m_canUpdateImage; } virtual void setImage( const QImage &image ); virtual void removeImage(); /* MemoryMeta::Album methods: */ /** * Re-read isCompilation, canUpdateCompilation, image, canUpdateImage from all * underlying tracks. */ void updateCachedValues(); private: Meta::ArtistPtr m_albumArtist; bool m_isCompilation; bool m_canUpdateCompilation; QImage m_image; bool m_canUpdateImage; }; class Composer : public Meta::Composer, public Base { public: Composer( const QString &name ) : MemoryMeta::Base( name ) {} virtual QString name() const { return MemoryMeta::Base::name(); } virtual Meta::TrackList tracks() { return MemoryMeta::Base::tracks(); } }; class Genre : public Meta::Genre, public Base { public: Genre( const QString &name ) : MemoryMeta::Base( name ) {} virtual QString name() const { return MemoryMeta::Base::name(); } virtual Meta::TrackList tracks() { return MemoryMeta::Base::tracks(); } }; class Year : public Meta::Year, public Base { public: Year( const QString &name ) : MemoryMeta::Base( name ) {} virtual QString name() const { return MemoryMeta::Base::name(); } virtual Meta::TrackList tracks() { return MemoryMeta::Base::tracks(); } }; class AMAROK_EXPORT Track : public Meta::Track { public: Track( const Meta::TrackPtr &originalTrack ); virtual ~Track(); /* Meta::MetaCapability methods */ virtual bool hasCapabilityInterface( Capabilities::Capability::Type type ) const { return m_track->hasCapabilityInterface( type ); } virtual Capabilities::Capability *createCapabilityInterface( Capabilities::Capability::Type type ) { return m_track->createCapabilityInterface( type ); } /* Meta::Base virtual methods */ virtual QString name() const { return m_track->name(); } /* Meta::Track virtual methods */ - virtual KUrl playableUrl() const { return m_track->playableUrl(); } + virtual QUrl playableUrl() const { return m_track->playableUrl(); } virtual QString prettyUrl() const { return m_track->prettyUrl(); } virtual QString uidUrl() const { return m_track->uidUrl(); } virtual QString notPlayableReason() const { return m_track->notPlayableReason(); } //these functions return the proxy track values virtual Meta::AlbumPtr album() const { return m_album; } virtual Meta::ArtistPtr artist() const { return m_artist; } virtual Meta::ComposerPtr composer() const { return m_composer; } virtual Meta::GenrePtr genre() const { return m_genre; } virtual Meta::YearPtr year() const { return m_year; } //TODO:implement labels virtual Meta::LabelList labels() const { return Meta::LabelList(); } virtual qreal bpm() const { return m_track->bpm(); } virtual QString comment() const { return m_track->comment(); } virtual qint64 length() const { return m_track->length(); } virtual int filesize() const { return m_track->filesize(); } virtual int sampleRate() const { return m_track->sampleRate(); } virtual int bitrate() const { return m_track->bitrate(); } virtual QDateTime createDate() const { return m_track->createDate(); } virtual QDateTime modifyDate() const { return m_track->modifyDate(); } virtual int trackNumber() const { return m_track->trackNumber(); } virtual int discNumber() const { return m_track->discNumber(); } virtual qreal replayGain( Meta::ReplayGainTag mode ) const { return m_track->replayGain( mode ); } virtual QString type() const { return m_track->type(); } virtual void prepareToPlay() { m_track->prepareToPlay(); } virtual void finishedPlaying( double fraction ) { m_track->finishedPlaying( fraction ); } virtual bool inCollection() const { return m_track->inCollection(); } virtual Collections::Collection *collection() const { return m_track->collection(); } virtual QString cachedLyrics() const { return m_track->cachedLyrics(); } virtual void setCachedLyrics( const QString &lyrics ) { m_track->setCachedLyrics( lyrics ); } //TODO: implement labels virtual void addLabel( const QString &label ) { Q_UNUSED( label ) } virtual void addLabel( const Meta::LabelPtr &label ) { Q_UNUSED( label ) } virtual void removeLabel( const Meta::LabelPtr &label ) { Q_UNUSED( label ) } virtual Meta::TrackEditorPtr editor(); virtual Meta::StatisticsPtr statistics(); // MemoryMeta::Track methods: /* All of these set* methods pass the pointer to KSharedPtr (thus memory-manage it), * remove this track from previous {Album,Artist,Composer,Genre,Year} entity (if any) * and add this track to newly set entity. (if non-null) * All these methods are reentrant, but not thread-safe: caller must ensure that * only one of the following methods is called at a time on a single instance. */ void setAlbum( Album *album ); void setArtist( Artist *artist ); void setComposer( Composer *composer ); void setGenre( Genre *genre ); void setYear( Year *year ); /** * Return the original track this track proxies. */ Meta::TrackPtr originalTrack() const { return m_track; } /** * Make notifyObservers() public so that MapChanger can call this */ using Meta::Track::notifyObservers; private: Meta::TrackPtr m_track; Meta::AlbumPtr m_album; Meta::ArtistPtr m_artist; Meta::ComposerPtr m_composer; Meta::GenrePtr m_genre; Meta::YearPtr m_year; }; /** * Helper class that facilitates adding, removing and changing tracks that are in * MemoryCollection. This class locks underlying MemoryCollection upon construction for * writing and releases the lock in destructor. * * Typical usage: * { * MemoryMeta::MapChanger changer( memoryCollectionPtr ); * Meta::Track newTrack = changer.addTrack( trackPtr ); * ... * changer.removeTrack( newTrack ); * } * * All methods in this class are re-entrant and it operates on MemoryCollection in * a thread-safe way: you can run MapChangers from multiple threads on a single * MemoryCollection at once. (each thread constructing MapChanger when needed and deleting * it as soon as possible) * * All methods can be called multiple times on a single instance and can be combined. */ class AMAROK_EXPORT MapChanger { public: MapChanger( MemoryCollection *memoryCollection ); ~MapChanger(); /** * Adds a track to MemoryCollection by proxying it using @see MemoryMeta::Track * track artist, album, genre, composer and year are replaced in MemoryMeta::Track * by relevant MemoryMeta entities, based on their value. Refuses to add a track * whose proxy is already in MemoryCollection (returns null pointer in this case) * * @return pointer to a newly created MemoryMeta::Track (may be null if not * successful) */ Meta::TrackPtr addTrack( Meta::TrackPtr track ); /** * Removes a track from MemoryCollection. Pays attention to remove artists, * albums, genres, composers and years that may become dangling in * MemoryCollection. * * @param track MemoryMeta track to remove, it doesn't matter if this is the track * returned by MapChanger::addTrack or the underlying one passed to * MapChanger::addTrack - the real track is looked up using its uidUrl in * MemoryCollection. * * @return shared pointer to underlying track of the deleted track, i.e. the track * that you passed to MapChanger::addTrack() originally. May be null pointer if * @param track is not found in collection or if it wasn't added using MapChanger. */ Meta::TrackPtr removeTrack( Meta::TrackPtr track ); /** * Reflects changes made to underlying track to its proxy track in * MemoryCollection and to MemoryCollection maps. * * The one who called MapChanger::addTrack() is responsible to call this method * every time it detects that some metadata of underlying track have changed * (perhaps by becoming its observer), even in minor fields such as comment. This * method instructs proxy track to call notifyObservers(). * * Please note that this method is currently unable to cope with changes * to track uidUrl(). If you really need it, change MemoryCollection's * trackMap manually _before_ calling this. * * @param track track whose metadata have changed, it doesn't matter if this is * the track returned by MapChanger::addTrack or the underlying one passed to * MapChanger::addTrack - the real track is looked up using its uidUrl in * MemoryCollection. Does nothing if @param track is not found in MemoryCollection. * * @return true if memory collection maps had to be changed, false for minor * changes where this is not required */ bool trackChanged( Meta::TrackPtr track ); private: /** * Worker for addTrack. * * @param track original underlying track - source of metadata * @param memoryTrack new track to add to MemoryCollection - target of metadata */ Meta::TrackPtr addExistingTrack( Meta::TrackPtr track, Track *memoryTrack ); /** * Return true if at least one of the tracks in @param needles is in * @param haystack, false otherwise. Comparison is done using track uidUrl. */ static bool hasTrackInMap( const Meta::TrackList &needles, const TrackMap &haystack ); /** * Return true if artist @param artist is referenced as albumArtist of one of the * albums from @param haystack. The match is done using Meta:ArtistPtr * operator==. */ static bool referencedAsAlbumArtist( const Meta::ArtistPtr &artist, const AlbumMap &haystack ); /** * Return true if @param first has different value than @param other. Specifically * returns true if one entity is null and the other non-null, but returns true if * both are null. */ static bool entitiesDiffer( const Meta::Base *first, const Meta::Base *second ); /** * Overload for albums, we compare album artist, isCollection and image too */ static bool entitiesDiffer( const Meta::Album *first, const Meta::Album *second ); MemoryCollection *m_mc; }; } #endif diff --git a/src/core-impl/collections/support/TrashCollectionLocation.cpp b/src/core-impl/collections/support/TrashCollectionLocation.cpp index a95b69f405..6780e086b9 100644 --- a/src/core-impl/collections/support/TrashCollectionLocation.cpp +++ b/src/core-impl/collections/support/TrashCollectionLocation.cpp @@ -1,139 +1,139 @@ /**************************************************************************************** * Copyright (c) 2010 Rick W. Chen * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "TrashCollectionLocation" #include "TrashCollectionLocation.h" #include "core/collections/CollectionLocationDelegate.h" #include "core/interfaces/Logger.h" #include "core/support/Components.h" #include "core/support/Debug.h" #include #include #include namespace Collections { TrashCollectionLocation::TrashCollectionLocation() : CollectionLocation() , m_trashConfirmed( false ) { } TrashCollectionLocation::~TrashCollectionLocation() { } QString TrashCollectionLocation::prettyLocation() const { return i18n( "Trash" ); } bool TrashCollectionLocation::isWritable() const { return true; } void -TrashCollectionLocation::copyUrlsToCollection( const QMap &sources, +TrashCollectionLocation::copyUrlsToCollection( const QMap &sources, const Transcoding::Configuration &configuration ) { DEBUG_BLOCK Q_UNUSED( configuration ); if( sources.isEmpty() ) { debug() << "Error: sources is empty"; abort(); return; } if( m_trashConfirmed ) { - KUrl::List files = sources.values(); - foreach( const KUrl &file, files ) + QList files = sources.values(); + foreach( const QUrl &file, files ) { if( !QFile::exists( file.toLocalFile() ) ) { debug() << "Error: file does not exist!" << file.toLocalFile(); abort(); return; } } KIO::CopyJob *job = KIO::trash( files, KIO::HideProgressInfo ); connect( job, SIGNAL(result(KJob*)), SLOT(slotTrashJobFinished(KJob*)) ); Meta::TrackList tracks = sources.keys(); m_trashJobs.insert( job, tracks ); QString name = tracks.takeFirst()->prettyName(); if( !tracks.isEmpty() ) { int max = 3; while( !tracks.isEmpty() && (max > 0) ) { name += QString( ", %1" ).arg( tracks.takeFirst()->prettyName() ); --max; } if( max == 0 && !tracks.isEmpty() ) name += " ..."; } Amarok::Components::logger()->newProgressOperation( job, i18n( "Moving to trash: %1", name ) ); } } void TrashCollectionLocation::showDestinationDialog( const Meta::TrackList &tracks, bool removeSources, const Transcoding::Configuration &configuration ) { Collections::CollectionLocationDelegate *delegate = Amarok::Components::collectionLocationDelegate(); m_trashConfirmed = delegate->reallyTrash( source(), tracks ); if( !m_trashConfirmed ) abort(); else CollectionLocation::showDestinationDialog( tracks, removeSources, configuration ); } void TrashCollectionLocation::slotTrashJobFinished( KJob *job ) { DEBUG_BLOCK if( job->error() ) { warning() << "An error occurred when moving a file to trash: " << job->errorString(); foreach( Meta::TrackPtr track, m_trashJobs.value( job ) ) source()->transferError( track, KIO::buildErrorString( job->error(), job->errorString() ) ); } else { foreach( Meta::TrackPtr track, m_trashJobs.value( job ) ) source()->transferSuccessful( track ); } m_trashJobs.remove( job ); job->deleteLater(); if( m_trashJobs.isEmpty() ) slotCopyOperationFinished(); } } //namespace Collections diff --git a/src/core-impl/collections/support/TrashCollectionLocation.h b/src/core-impl/collections/support/TrashCollectionLocation.h index 239a9778b2..d5a687b705 100644 --- a/src/core-impl/collections/support/TrashCollectionLocation.h +++ b/src/core-impl/collections/support/TrashCollectionLocation.h @@ -1,58 +1,58 @@ /**************************************************************************************** * Copyright (c) 2010 Rick W. Chen * * * * 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, see . * ****************************************************************************************/ #ifndef TRASHCOLLECTIONLOCATION_H #define TRASHCOLLECTIONLOCATION_H #include "core/collections/CollectionLocation.h" class KJob; namespace Collections { /** * Utility class that allows moving tracks to the KIO trash using standard * CollectionLocation API. It is not intented to be a collection, but more * as a black hole destination. */ class TrashCollectionLocation : public CollectionLocation { Q_OBJECT public: TrashCollectionLocation(); ~TrashCollectionLocation(); QString prettyLocation() const; bool isWritable() const; protected: - void copyUrlsToCollection( const QMap &sources, + void copyUrlsToCollection( const QMap &sources, const Transcoding::Configuration &configuration ); void showDestinationDialog( const Meta::TrackList &tracks, bool removeSources, const Transcoding::Configuration &configuration ); private slots: void slotTrashJobFinished( KJob *job ); private: bool m_trashConfirmed; QHash m_trashJobs; }; } //namespace Collections #endif // TRASHCOLLECTIONLOCATION_H diff --git a/src/core-impl/collections/umscollection/UmsCollection.cpp b/src/core-impl/collections/umscollection/UmsCollection.cpp index 70e57b84f3..906f66a73a 100644 --- a/src/core-impl/collections/umscollection/UmsCollection.cpp +++ b/src/core-impl/collections/umscollection/UmsCollection.cpp @@ -1,744 +1,746 @@ /**************************************************************************************** * Copyright (c) 2011 Bart Cerneels * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "UmsCollection" #include "UmsCollection.h" #include "amarokconfig.h" #include "ui_UmsConfiguration.h" #include "collectionscanner/Track.h" #include "core/capabilities/ActionsCapability.h" #include "core/interfaces/Logger.h" #include "core/meta/Meta.h" #include "core/support/Components.h" #include "core/support/Debug.h" #include "core-impl/collections/support/MemoryQueryMaker.h" #include "core-impl/collections/support/MemoryMeta.h" #include "core-impl/collections/umscollection/UmsCollectionLocation.h" #include "core-impl/collections/umscollection/UmsTranscodeCapability.h" #include "core-impl/meta/file/File.h" #include "dialogs/OrganizeCollectionDialog.h" #include "dialogs/TrackOrganizer.h" //TODO: move to core/utils #include "scanner/GenericScanManager.h" #include #include #include #include #include #include #include #include #include #include -#include +#include #include #include AMAROK_EXPORT_COLLECTION( UmsCollectionFactory, umscollection ) UmsCollectionFactory::UmsCollectionFactory( QObject *parent, const QVariantList &args ) : CollectionFactory( parent, args ) { m_info = KPluginInfo( "amarok_collection-umscollection.desktop", "services" ); } UmsCollectionFactory::~UmsCollectionFactory() { } void UmsCollectionFactory::init() { connect( Solid::DeviceNotifier::instance(), SIGNAL(deviceAdded(QString)), SLOT(slotAddSolidDevice(QString)) ); connect( Solid::DeviceNotifier::instance(), SIGNAL(deviceRemoved(QString)), SLOT(slotRemoveSolidDevice(QString)) ); // detect UMS devices that were already connected on startup QString query( "IS StorageAccess" ); QList devices = Solid::Device::listFromQuery( query ); foreach( const Solid::Device &device, devices ) { if( identifySolidDevice( device.udi() ) ) createCollectionForSolidDevice( device.udi() ); } m_initialized = true; } void UmsCollectionFactory::slotAddSolidDevice( const QString &udi ) { if( m_collectionMap.contains( udi ) ) return; // a device added twice (?) if( identifySolidDevice( udi ) ) createCollectionForSolidDevice( udi ); } void UmsCollectionFactory::slotAccessibilityChanged( bool accessible, const QString &udi ) { if( accessible ) slotAddSolidDevice( udi ); else slotRemoveSolidDevice( udi ); } void UmsCollectionFactory::slotRemoveSolidDevice( const QString &udi ) { UmsCollection *collection = m_collectionMap.take( udi ); if( collection ) collection->slotDestroy(); } void UmsCollectionFactory::slotRemoveAndTeardownSolidDevice( const QString &udi ) { UmsCollection *collection = m_collectionMap.take( udi ); if( collection ) collection->slotEject(); } void UmsCollectionFactory::slotCollectionDestroyed( QObject *collection ) { // remove destroyed collection from m_collectionMap QMutableMapIterator it( m_collectionMap ); while( it.hasNext() ) { it.next(); if( (QObject *) it.value() == collection ) it.remove(); } } bool UmsCollectionFactory::identifySolidDevice( const QString &udi ) const { Solid::Device device( udi ); if( !device.is() ) return false; // HACK to exlude iPods until UMS and iPod have common collection factory if( device.vendor().contains( "Apple", Qt::CaseInsensitive ) ) return false; // everything okay, check whether the device is a data CD if( device.is() ) { const Solid::OpticalDisc *disc = device.as(); if( disc && ( disc->availableContent() & Solid::OpticalDisc::Data ) ) return true; return false; } // check whether there is parent USB StorageDrive device while( device.isValid() ) { if( device.is() ) { Solid::StorageDrive *sd = device.as(); if( sd->driveType() == Solid::StorageDrive::CdromDrive ) return false; // USB Flash discs are usually hotpluggable, SD/MMC card slots are usually removable return sd->isHotpluggable() || sd->isRemovable(); } device = device.parent(); } return false; // no valid parent USB StorageDrive } void UmsCollectionFactory::createCollectionForSolidDevice( const QString &udi ) { DEBUG_BLOCK Solid::Device device( udi ); Solid::StorageAccess *ssa = device.as(); if( !ssa ) { warning() << __PRETTY_FUNCTION__ << "called for non-StorageAccess device!?!"; return; } if( ssa->isIgnored() ) { debug() << "device" << udi << "ignored, ignoring :-)"; return; } // we are definitely interested in this device, listen for accessibility changes disconnect( ssa, SIGNAL(accessibilityChanged(bool,QString)), this, 0 ); connect( ssa, SIGNAL(accessibilityChanged(bool,QString)), SLOT(slotAccessibilityChanged(bool,QString)) ); if( !ssa->isAccessible() ) { debug() << "device" << udi << "not accessible, ignoring for now"; return; } UmsCollection *collection = new UmsCollection( device ); m_collectionMap.insert( udi, collection ); // when the collection is destroyed by someone else, remove it from m_collectionMap: connect( collection, SIGNAL(destroyed(QObject*)), SLOT(slotCollectionDestroyed(QObject*)) ); // try to gracefully destroy collection when unmounting is requested using // external means: (Device notifier plasmoid etc.). Because the original action could // fail if we hold some files on the device open, we try to tearDown the device too. connect( ssa, SIGNAL(teardownRequested(QString)), SLOT(slotRemoveAndTeardownSolidDevice(QString)) ); emit newCollection( collection ); } //UmsCollection QString UmsCollection::s_settingsFileName( ".is_audio_player" ); QString UmsCollection::s_musicFolderKey( "audio_folder" ); QString UmsCollection::s_musicFilenameSchemeKey( "music_filenamescheme" ); QString UmsCollection::s_vfatSafeKey( "vfat_safe" ); QString UmsCollection::s_asciiOnlyKey( "ascii_only" ); QString UmsCollection::s_postfixTheKey( "ignore_the" ); QString UmsCollection::s_replaceSpacesKey( "replace_spaces" ); QString UmsCollection::s_regexTextKey( "regex_text" ); QString UmsCollection::s_replaceTextKey( "replace_text" ); QString UmsCollection::s_podcastFolderKey( "podcast_folder" ); QString UmsCollection::s_autoConnectKey( "use_automatically" ); QString UmsCollection::s_collectionName( "collection_name" ); QString UmsCollection::s_transcodingGroup( "transcoding" ); UmsCollection::UmsCollection( Solid::Device device ) : Collection() , m_device( device ) , m_mc( 0 ) , m_tracksParsed( false ) , m_autoConnect( false ) , m_musicFilenameScheme( "%artist%/%album%/%track% %title%" ) , m_vfatSafe( true ) , m_asciiOnly( false ) , m_postfixThe( false ) , m_replaceSpaces( false ) , m_regexText( QString() ) , m_replaceText( QString() ) , m_collectionName( QString() ) , m_scanManager( 0 ) , m_lastUpdated( 0 ) { debug() << "Creating UmsCollection for device with udi: " << m_device.udi(); m_updateTimer.setSingleShot( true ); connect( this, SIGNAL(startUpdateTimer()), SLOT(slotStartUpdateTimer()) ); connect( &m_updateTimer, SIGNAL(timeout()), SLOT(collectionUpdated()) ); m_configureAction = new QAction( KIcon( "configure" ), i18n( "&Configure Device" ), this ); m_configureAction->setProperty( "popupdropper_svg_id", "configure" ); connect( m_configureAction, SIGNAL(triggered()), SLOT(slotConfigure()) ); m_parseAction = new QAction( KIcon( "checkbox" ), i18n( "&Activate This Collection" ), this ); m_parseAction->setProperty( "popupdropper_svg_id", "edit" ); connect( m_parseAction, SIGNAL(triggered()), this, SLOT(slotParseActionTriggered()) ); m_ejectAction = new QAction( KIcon( "media-eject" ), i18n( "&Eject Device" ), const_cast( this ) ); m_ejectAction->setProperty( "popupdropper_svg_id", "eject" ); connect( m_ejectAction, SIGNAL(triggered()), SLOT(slotEject()) ); init(); } UmsCollection::~UmsCollection() { DEBUG_BLOCK } void UmsCollection::init() { Solid::StorageAccess *storageAccess = m_device.as(); m_mountPoint = storageAccess->filePath(); Solid::StorageVolume *ssv = m_device.as(); m_collectionId = ssv ? ssv->uuid() : m_device.udi(); debug() << "Mounted at: " << m_mountPoint << "collection id:" << m_collectionId; // read .is_audio_player from filesystem KConfig config( m_mountPoint + '/' + s_settingsFileName, KConfig::SimpleConfig ); KConfigGroup entries = config.group( QString() ); // default group if( entries.hasKey( s_musicFolderKey ) ) { - m_musicPath = KUrl( m_mountPoint ); - m_musicPath.addPath( entries.readPathEntry( s_musicFolderKey, QString() ) ); + m_musicPath = QUrl( m_mountPoint ); + m_musicPath = m_musicPath.adjusted(QUrl::StripTrailingSlash); + m_musicPath.setPath(m_musicPath.path() + '/' + ( entries.readPathEntry( s_musicFolderKey, QString() ) )); m_musicPath.cleanPath(); if( !QDir( m_musicPath.toLocalFile() ).exists() ) { QString message = i18n( "File %1 suggests that we should use %2 " "as music folder on the device, but it doesn't exist. Falling back to " "%3 instead", m_mountPoint + '/' + s_settingsFileName, m_musicPath.toLocalFile(), m_mountPoint ); Amarok::Components::logger()->longMessage( message, Amarok::Logger::Warning ); m_musicPath = m_mountPoint; } } else if( !entries.keyList().isEmpty() ) // config file exists, but has no s_musicFolderKey -> music should be disabled - m_musicPath = KUrl(); + m_musicPath = QUrl(); else m_musicPath = m_mountPoint; // related BR 259849 QString scheme = entries.readEntry( s_musicFilenameSchemeKey ); m_musicFilenameScheme = !scheme.isEmpty() ? scheme : m_musicFilenameScheme; m_vfatSafe = entries.readEntry( s_vfatSafeKey, m_vfatSafe ); m_asciiOnly = entries.readEntry( s_asciiOnlyKey, m_asciiOnly ); m_postfixThe = entries.readEntry( s_postfixTheKey, m_postfixThe ); m_replaceSpaces = entries.readEntry( s_replaceSpacesKey, m_replaceSpaces ); m_regexText = entries.readEntry( s_regexTextKey, m_regexText ); m_replaceText = entries.readEntry( s_replaceTextKey, m_replaceText ); if( entries.hasKey( s_podcastFolderKey ) ) { - m_podcastPath = KUrl( m_mountPoint ); - m_podcastPath.addPath( entries.readPathEntry( s_podcastFolderKey, QString() ) ); + m_podcastPath = QUrl( m_mountPoint ); + m_podcastPath = m_podcastPath.adjusted(QUrl::StripTrailingSlash); + m_podcastPath.setPath(m_podcastPath.path() + '/' + ( entries.readPathEntry( s_podcastFolderKey, QString() ) )); m_podcastPath.cleanPath(); } m_autoConnect = entries.readEntry( s_autoConnectKey, m_autoConnect ); m_collectionName = entries.readEntry( s_collectionName, m_collectionName ); m_mc = QSharedPointer(new MemoryCollection()); if( m_autoConnect ) QTimer::singleShot( 0, this, SLOT(slotParseTracks()) ); } bool -UmsCollection::possiblyContainsTrack( const KUrl &url ) const +UmsCollection::possiblyContainsTrack( const QUrl &url ) const { //not initialized yet. if( m_mc.isNull() ) return false; QString u = QUrl::fromPercentEncoding( url.url().toUtf8() ); return u.startsWith( m_mountPoint ) || u.startsWith( "file://" + m_mountPoint ); } Meta::TrackPtr -UmsCollection::trackForUrl( const KUrl &url ) +UmsCollection::trackForUrl( const QUrl &url ) { //not initialized yet. if( m_mc.isNull() ) return Meta::TrackPtr(); QString uid = QUrl::fromPercentEncoding( url.url().toUtf8() ); if( uid.startsWith("file://") ) uid = uid.remove( 0, 7 ); return m_mc->trackMap().value( uid, Meta::TrackPtr() ); } QueryMaker * UmsCollection::queryMaker() { return new MemoryQueryMaker( m_mc.toWeakRef(), collectionId() ); } QString UmsCollection::uidUrlProtocol() const { return QString( "file://" ); } QString UmsCollection::collectionId() const { return m_collectionId; } QString UmsCollection::prettyName() const { QString actualName; if( !m_collectionName.isEmpty() ) actualName = m_collectionName; else if( !m_device.description().isEmpty() ) actualName = m_device.description(); else { actualName = m_device.vendor().simplified(); if( !actualName.isEmpty() ) actualName += ' '; actualName += m_device.product().simplified(); } if( m_tracksParsed ) return actualName; else return i18nc( "Name of the USB Mass Storage collection that has not yet been " "activated. See also the 'Activate This Collection' action; %1 is " "actual collection name", "%1 (not activated)", actualName ); } KIcon UmsCollection::icon() const { if( m_device.icon().isEmpty() ) return KIcon( "drive-removable-media-usb-pendrive" ); else return KIcon( m_device.icon() ); } bool UmsCollection::hasCapacity() const { if( m_device.isValid() && m_device.is() ) return m_device.as()->isAccessible(); return false; } float UmsCollection::usedCapacity() const { return KDiskFreeSpaceInfo::freeSpaceInfo( m_mountPoint ).used(); } float UmsCollection::totalCapacity() const { return KDiskFreeSpaceInfo::freeSpaceInfo( m_mountPoint ).size(); } CollectionLocation * UmsCollection::location() { return new UmsCollectionLocation( this ); } bool UmsCollection::isOrganizable() const { return isWritable(); } bool UmsCollection::hasCapabilityInterface( Capabilities::Capability::Type type ) const { switch( type ) { case Capabilities::Capability::Actions: case Capabilities::Capability::Transcode: return true; default: return false; } } Capabilities::Capability * UmsCollection::createCapabilityInterface( Capabilities::Capability::Type type ) { switch( type ) { case Capabilities::Capability::Actions: { QList actions; if( m_tracksParsed ) { actions << m_configureAction; actions << m_ejectAction; } else { actions << m_parseAction; } return new Capabilities::ActionsCapability( actions ); } case Capabilities::Capability::Transcode: return new UmsTranscodeCapability( m_mountPoint + '/' + s_settingsFileName, s_transcodingGroup ); default: return 0; } } void UmsCollection::metadataChanged( Meta::TrackPtr track ) { if( MemoryMeta::MapChanger( m_mc.data() ).trackChanged( track ) ) // big-enough change: emit startUpdateTimer(); } -KUrl +QUrl UmsCollection::organizedUrl( Meta::TrackPtr track, const QString &fileExtension ) const { TrackOrganizer trackOrganizer( Meta::TrackList() << track ); //%folder% prefix required to get absolute url. trackOrganizer.setFormatString( "%collectionroot%/" + m_musicFilenameScheme + ".%filetype%" ); trackOrganizer.setVfatSafe( m_vfatSafe ); trackOrganizer.setAsciiOnly( m_asciiOnly ); trackOrganizer.setFolderPrefix( m_musicPath.path() ); trackOrganizer.setPostfixThe( m_postfixThe ); trackOrganizer.setReplaceSpaces( m_replaceSpaces ); trackOrganizer.setReplace( m_regexText, m_replaceText ); if( !fileExtension.isEmpty() ) trackOrganizer.setTargetFileExtension( fileExtension ); - return KUrl( trackOrganizer.getDestinations().value( track ) ); + return QUrl( trackOrganizer.getDestinations().value( track ) ); } void UmsCollection::slotDestroy() { //TODO: stop scanner if running //unregister PlaylistProvider //CollectionManager will call destructor. emit remove(); } void UmsCollection::slotEject() { slotDestroy(); Solid::StorageAccess *storageAccess = m_device.as(); storageAccess->teardown(); } void -UmsCollection::slotTrackAdded( KUrl location ) +UmsCollection::slotTrackAdded( QUrl location ) { Q_ASSERT( m_musicPath.isParentOf( location ) ); MetaFile::Track *fileTrack = new MetaFile::Track( location ); fileTrack->setCollection( this ); Meta::TrackPtr fileTrackPtr = Meta::TrackPtr( fileTrack ); Meta::TrackPtr proxyTrack = MemoryMeta::MapChanger( m_mc.data() ).addTrack( fileTrackPtr ); if( proxyTrack ) { subscribeTo( fileTrackPtr ); emit startUpdateTimer(); } else warning() << __PRETTY_FUNCTION__ << "Failed to add" << fileTrackPtr->playableUrl() << "to MemoryCollection. Perhaps already there?!?"; } void UmsCollection::slotTrackRemoved( const Meta::TrackPtr &track ) { Meta::TrackPtr removedTrack = MemoryMeta::MapChanger( m_mc.data() ).removeTrack( track ); if( removedTrack ) { unsubscribeFrom( removedTrack ); // we only added MetaFile::Tracks, following static cast is safe static_cast( removedTrack.data() )->setCollection( 0 ); emit startUpdateTimer(); } else warning() << __PRETTY_FUNCTION__ << "Failed to remove" << track->playableUrl() << "from MemoryCollection. Perhaps it was never there?"; } void UmsCollection::collectionUpdated() { m_lastUpdated = QDateTime::currentMSecsSinceEpoch(); emit updated(); } void UmsCollection::slotParseTracks() { if( !m_scanManager ) { m_scanManager = new GenericScanManager( this ); connect( m_scanManager, SIGNAL(directoryScanned(QSharedPointer)), SLOT(slotDirectoryScanned(QSharedPointer)) ); } m_tracksParsed = true; - m_scanManager->requestScan( QList() << m_musicPath, GenericScanManager::FullScan ); + m_scanManager->requestScan( QList() << m_musicPath, GenericScanManager::FullScan ); } void UmsCollection::slotParseActionTriggered() { if( m_mc->trackMap().isEmpty() ) QTimer::singleShot( 0, this, SLOT(slotParseTracks()) ); } void UmsCollection::slotConfigure() { KDialog umsSettingsDialog; QWidget *settingsWidget = new QWidget( &umsSettingsDialog ); QScopedPointer tc( create() ); Ui::UmsConfiguration *settings = new Ui::UmsConfiguration(); settings->setupUi( settingsWidget ); settings->m_autoConnect->setChecked( m_autoConnect ); settings->m_musicFolder->setMode( KFile::Directory ); settings->m_musicCheckBox->setChecked( !m_musicPath.isEmpty() ); settings->m_musicWidget->setEnabled( settings->m_musicCheckBox->isChecked() ); - settings->m_musicFolder->setUrl( m_musicPath.isEmpty() ? KUrl( m_mountPoint ) : m_musicPath ); + settings->m_musicFolder->setUrl( m_musicPath.isEmpty() ? QUrl( m_mountPoint ) : m_musicPath ); settings->m_transcodeConfig->fillInChoices( tc->savedConfiguration() ); settings->m_podcastFolder->setMode( KFile::Directory ); settings->m_podcastCheckBox->setChecked( !m_podcastPath.isEmpty() ); settings->m_podcastWidget->setEnabled( settings->m_podcastCheckBox->isChecked() ); - settings->m_podcastFolder->setUrl( m_podcastPath.isEmpty() ? KUrl( m_mountPoint ) + settings->m_podcastFolder->setUrl( m_podcastPath.isEmpty() ? QUrl( m_mountPoint ) : m_podcastPath ); settings->m_collectionName->setText( prettyName() ); OrganizeCollectionWidget layoutWidget( &umsSettingsDialog ); //TODO: save the setting that are normally written in onAccept() // connect( this, SIGNAL(accepted()), &layoutWidget, SLOT(onAccept()) ); QVBoxLayout layout( &umsSettingsDialog ); layout.addWidget( &layoutWidget ); settings->m_filenameSchemeBox->setLayout( &layout ); //hide the unuse preset selector. //TODO: change the presets to concurrent presets for regular albums v.s. compilations // layoutWidget.setformatPresetVisible( false ); layoutWidget.setScheme( m_musicFilenameScheme ); OrganizeCollectionOptionWidget optionsWidget; optionsWidget.setVfatCompatible( m_vfatSafe ); optionsWidget.setAsciiOnly( m_asciiOnly ); optionsWidget.setPostfixThe( m_postfixThe ); optionsWidget.setReplaceSpaces( m_replaceSpaces ); optionsWidget.setRegexpText( m_regexText ); optionsWidget.setReplaceText( m_replaceText ); layout.addWidget( &optionsWidget ); umsSettingsDialog.setButtons( KDialog::Ok | KDialog::Cancel ); umsSettingsDialog.setMainWidget( settingsWidget ); umsSettingsDialog.setWindowTitle( i18n( "Configure USB Mass Storage Device" ) ); if( umsSettingsDialog.exec() == QDialog::Accepted ) { debug() << "accepted"; if( settings->m_musicCheckBox->isChecked() ) { if( settings->m_musicFolder->url() != m_musicPath ) { debug() << "music location changed from " << m_musicPath.toLocalFile() << " to "; debug() << settings->m_musicFolder->url().toLocalFile(); m_musicPath = settings->m_musicFolder->url(); //TODO: reparse music } QString scheme = layoutWidget.getParsableScheme().simplified(); //protect against empty string. if( !scheme.isEmpty() ) m_musicFilenameScheme = scheme; } else { debug() << "music support is disabled"; - m_musicPath = KUrl(); + m_musicPath = QUrl(); //TODO: remove all tracks from the MemoryCollection. } m_asciiOnly = optionsWidget.asciiOnly(); m_postfixThe = optionsWidget.postfixThe(); m_replaceSpaces = optionsWidget.replaceSpaces(); m_regexText = optionsWidget.regexpText(); m_replaceText = optionsWidget.replaceText(); m_collectionName = settings->m_collectionName->text(); if( settings->m_podcastCheckBox->isChecked() ) { if( settings->m_podcastFolder->url() != m_podcastPath ) { debug() << "podcast location changed from " << m_podcastPath << " to "; debug() << settings->m_podcastFolder->url().url(); m_podcastPath = settings->m_podcastFolder->url().toLocalFile(); //TODO: reparse podcasts } } else { debug() << "podcast support is disabled"; - m_podcastPath = KUrl(); + m_podcastPath = QUrl(); //TODO: remove the PodcastProvider } m_autoConnect = settings->m_autoConnect->isChecked(); if( !m_musicPath.isEmpty() && m_autoConnect ) QTimer::singleShot( 0, this, SLOT(slotParseTracks()) ); // write the data to the on-disk file KConfig config( m_mountPoint + '/' + s_settingsFileName, KConfig::SimpleConfig ); KConfigGroup entries = config.group( QString() ); // default group if( !m_musicPath.isEmpty() ) - entries.writePathEntry( s_musicFolderKey, KUrl::relativePath( m_mountPoint, + entries.writePathEntry( s_musicFolderKey, QUrl::relativePath( m_mountPoint, m_musicPath.toLocalFile() ) ); else entries.deleteEntry( s_musicFolderKey ); entries.writeEntry( s_musicFilenameSchemeKey, m_musicFilenameScheme ); entries.writeEntry( s_vfatSafeKey, m_vfatSafe ); entries.writeEntry( s_asciiOnlyKey, m_asciiOnly ); entries.writeEntry( s_postfixTheKey, m_postfixThe ); entries.writeEntry( s_replaceSpacesKey, m_replaceSpaces ); entries.writeEntry( s_regexTextKey, m_regexText ); entries.writeEntry( s_replaceTextKey, m_replaceText ); if( !m_podcastPath.isEmpty() ) - entries.writePathEntry( s_podcastFolderKey, KUrl::relativePath( m_mountPoint, + entries.writePathEntry( s_podcastFolderKey, QUrl::relativePath( m_mountPoint, m_podcastPath.toLocalFile() ) ); else entries.deleteEntry( s_podcastFolderKey ); entries.writeEntry( s_autoConnectKey, m_autoConnect ); entries.writeEntry( s_collectionName, m_collectionName ); config.sync(); tc->setSavedConfiguration( settings->m_transcodeConfig->currentChoice() ); } delete settings; } void UmsCollection::slotDirectoryScanned( QSharedPointer dir ) { debug() << "directory scanned: " << dir->path(); if( dir->tracks().isEmpty() ) { debug() << "does not have tracks"; return; } foreach( const CollectionScanner::Track *scannerTrack, dir->tracks() ) { //TODO: use proxy tracks so no real file read is required // following method calls startUpdateTimer(), no need to emit updated() slotTrackAdded( scannerTrack->path() ); } //TODO: read playlists } void UmsCollection::slotStartUpdateTimer() { // there are no concurrency problems, this method can only be called from the main // thread and that's where the timer fires if( m_updateTimer.isActive() ) return; // already running, nothing to do // number of milliseconds to next desired update, may be negative int timeout = m_lastUpdated + 1000 - QDateTime::currentMSecsSinceEpoch(); // give at least 50 msecs to catch multi-tracks edits nicely on the first frame m_updateTimer.start( qBound( 50, timeout, 1000 ) ); } diff --git a/src/core-impl/collections/umscollection/UmsCollection.h b/src/core-impl/collections/umscollection/UmsCollection.h index 0bc620cfd2..2fd42b97e2 100644 --- a/src/core-impl/collections/umscollection/UmsCollection.h +++ b/src/core-impl/collections/umscollection/UmsCollection.h @@ -1,243 +1,243 @@ /**************************************************************************************** * Copyright (c) 2009 Alejandro Wainzinger * * * * 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, see . * ****************************************************************************************/ #ifndef UMSCOLLECTION_H #define UMSCOLLECTION_H #include "collectionscanner/Directory.h" #include "core/collections/Collection.h" #include "core/meta/Observer.h" #include "core-impl/collections/support/MemoryCollection.h" #include #include #include #include #include class GenericScanManager; class UmsPodcastProvider; class UmsCollection; class UmsCollectionLocation; class QAction; using namespace Collections; class UmsCollectionFactory : public CollectionFactory { Q_OBJECT public: UmsCollectionFactory( QObject *parent, const QVariantList &args ); virtual ~UmsCollectionFactory(); virtual void init(); private slots: /** * Called when solid notifier detects a new device has been added */ void slotAddSolidDevice( const QString &udi ); /** * Called when solid StorageAccess device we are interested in is mounted or * unmounted */ void slotAccessibilityChanged( bool accessible, const QString &udi ); /** * Called when solid notifier detects a device has been removed */ void slotRemoveSolidDevice( const QString &udi ); /** * Like @see slotRemoveSolidDevice(), but instructs Collection to eject the * device after it has performed necessary teardown operations. * * Called when user wants to unmount the device from for example Device Notifier */ void slotRemoveAndTeardownSolidDevice( const QString &udi ); /** * Called when "tracked" collection is destroyed */ void slotCollectionDestroyed( QObject *collection ); private: /** * Checks whether a solid device is a USB mass-storage one */ bool identifySolidDevice( const QString &udi ) const; /** * Attempts to create appropriate collection for already identified solid device * @param udi. Should emit newCollection() if the collection was successfully * created and should become visible to the user. */ void createCollectionForSolidDevice( const QString &udi ); // maps device udi to active UMS collections QMap m_collectionMap; }; class UmsCollection : public Collection, public Meta::Observer { Q_OBJECT public: // inherited methods UmsCollection( Solid::Device device ); virtual ~UmsCollection(); /* TrackProvider methods */ - virtual bool possiblyContainsTrack( const KUrl &url ) const; - virtual Meta::TrackPtr trackForUrl( const KUrl &url ); + virtual bool possiblyContainsTrack( const QUrl &url ) const; + virtual Meta::TrackPtr trackForUrl( const QUrl &url ); /* Collection methods */ virtual QueryMaker *queryMaker(); virtual QString uidUrlProtocol() const; virtual QString collectionId() const; virtual QString prettyName() const; virtual KIcon icon() const; virtual bool hasCapacity() const; virtual float usedCapacity() const; virtual float totalCapacity() const; virtual CollectionLocation *location(); virtual bool isOrganizable() const; /* Capability-related methods */ virtual bool hasCapabilityInterface( Capabilities::Capability::Type type ) const; virtual Capabilities::Capability *createCapabilityInterface( Capabilities::Capability::Type type ); /* Meta::Observer methods */ virtual void metadataChanged( Meta::TrackPtr track ); using Meta::Observer::metadataChanged; // silence compiler warning about hidder overloads /* own methods */ - const KUrl &musicPath() const { return m_musicPath; } - const KUrl &podcastPath() const { return m_podcastPath; } + const QUrl &musicPath() const { return m_musicPath; } + const QUrl &podcastPath() const { return m_podcastPath; } /** * Get location where track @param track should be transferred to. * * @param fileExtension new extension to use. Leave empty if you don't wish to * change file extension */ - KUrl organizedUrl( Meta::TrackPtr track, const QString &fileExtension = QString() ) const; + QUrl organizedUrl( Meta::TrackPtr track, const QString &fileExtension = QString() ) const; QSharedPointer memoryCollection() const { return m_mc; } signals: /** * Start a count-down that emits updated() signal after it expires. * Resets the timer to original timeout if already running. This is to ensure * that we emit update() max. once per for batch updates. * * Timers can only be started from "their" thread so use signals & slots for that. */ void startUpdateTimer(); public slots: /** * Destroy the collection (by emitting remove) */ void slotDestroy(); /** * Destroy the collection and try to eject the device from system */ void slotEject(); - void slotTrackAdded( KUrl trackLocation ); + void slotTrackAdded( QUrl trackLocation ); void slotTrackRemoved( const Meta::TrackPtr &track ); private slots: /** * Update m_lastUpdated timestamp and emit updated() */ void collectionUpdated(); void slotParseTracks(); void slotParseActionTriggered(); void slotConfigure(); void slotDirectoryScanned( QSharedPointer dir ); /** * Starts a timer that ensures we emit updated() signal sometime in future. */ void slotStartUpdateTimer(); private: /** extended constructor */ void init(); //static variables relating to the on-disk configuration file static QString s_settingsFileName; static QString s_musicFolderKey; static QString s_musicFilenameSchemeKey; static QString s_vfatSafeKey; static QString s_asciiOnlyKey; static QString s_postfixTheKey; static QString s_replaceSpacesKey; static QString s_regexTextKey; static QString s_replaceTextKey; static QString s_podcastFolderKey; static QString s_autoConnectKey; static QString s_collectionName; static QString s_transcodingGroup; Solid::Device m_device; QSharedPointer m_mc; bool m_tracksParsed; bool m_autoConnect; QString m_mountPoint; - KUrl m_musicPath; - KUrl m_podcastPath; + QUrl m_musicPath; + QUrl m_podcastPath; QString m_musicFilenameScheme; bool m_vfatSafe; bool m_asciiOnly; bool m_postfixThe; bool m_replaceSpaces; QString m_regexText; QString m_replaceText; QString m_collectionName; QString m_collectionId; GenericScanManager *m_scanManager; KDirWatch m_watcher; QStringList m_supportedMimeTypes; UmsPodcastProvider *m_podcastProvider; QAction *m_parseAction; QAction *m_configureAction; QAction *m_ejectAction; QTimer m_updateTimer; qint64 m_lastUpdated; /* msecs since epoch */ }; #endif diff --git a/src/core-impl/collections/umscollection/UmsCollectionLocation.cpp b/src/core-impl/collections/umscollection/UmsCollectionLocation.cpp index cef90d780b..a29ee82abc 100644 --- a/src/core-impl/collections/umscollection/UmsCollectionLocation.cpp +++ b/src/core-impl/collections/umscollection/UmsCollectionLocation.cpp @@ -1,272 +1,272 @@ /**************************************************************************************** * Copyright (c) 2011 Bart Cerneels * * * * 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, see . * ****************************************************************************************/ #include "UmsCollectionLocation.h" #include "core/support/Components.h" #include "core/support/Debug.h" #include "core/interfaces/Logger.h" #include "core/transcoding/TranscodingController.h" #include "core-impl/meta/file/File.h" #include "transcoding/TranscodingJob.h" #include #include #include -#include +#include #include #include UmsCollectionLocation::UmsCollectionLocation( UmsCollection *umsCollection ) : CollectionLocation( umsCollection ) , m_umsCollection( umsCollection ) { } UmsCollectionLocation::~UmsCollectionLocation() { } QString UmsCollectionLocation::prettyLocation() const { - return m_umsCollection->musicPath().toLocalFile( KUrl::RemoveTrailingSlash ); + return m_umsCollection->musicPath().toLocalFile( QUrl::RemoveTrailingSlash ); } QStringList UmsCollectionLocation::actualLocation() const { return QStringList() << prettyLocation(); } bool UmsCollectionLocation::isWritable() const { const QFileInfo info( m_umsCollection->musicPath().toLocalFile() ); return info.isWritable(); } bool UmsCollectionLocation::isOrganizable() const { return isWritable(); } void -UmsCollectionLocation::copyUrlsToCollection( const QMap &sources, +UmsCollectionLocation::copyUrlsToCollection( const QMap &sources, const Transcoding::Configuration &configuration ) { //TODO: disable scanning until we are done with copying UmsTransferJob *transferJob = new UmsTransferJob( this, configuration ); - QMapIterator i( sources ); + QMapIterator i( sources ); while( i.hasNext() ) { i.next(); Meta::TrackPtr track = i.key(); - KUrl destination; + QUrl destination; bool isJustCopy = configuration.isJustCopy( track ); if( isJustCopy ) destination = m_umsCollection->organizedUrl( track ); else destination = m_umsCollection->organizedUrl( track, Amarok::Components:: transcodingController()->format( configuration.encoder() )->fileExtension() ); debug() << "destination is " << destination.toLocalFile(); - QDir dir( destination.directory() ); + QDir dir( destination.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path() ); if( !dir.exists() && !dir.mkpath( "." ) ) { error() << "could not create directory to copy into."; abort(); } m_sourceUrlToTrackMap.insert( i.value(), track ); // needed for slotTrackTransferred() if( isJustCopy ) transferJob->addCopy( i.value(), destination ); else transferJob->addTranscode( i.value(), destination ); } - connect( transferJob, SIGNAL(sourceFileTransferDone(KUrl)), - this, SLOT(slotTrackTransferred(KUrl)) ); - connect( transferJob, SIGNAL(fileTransferDone(KUrl)), - m_umsCollection, SLOT(slotTrackAdded(KUrl)) ); + connect( transferJob, SIGNAL(sourceFileTransferDone(QUrl)), + this, SLOT(slotTrackTransferred(QUrl)) ); + connect( transferJob, SIGNAL(fileTransferDone(QUrl)), + m_umsCollection, SLOT(slotTrackAdded(QUrl)) ); connect( transferJob, SIGNAL(finished(KJob*)), this, SLOT(slotCopyOperationFinished()) ); QString loggerText = operationInProgressText( configuration, sources.count(), m_umsCollection->prettyName() ); Amarok::Components::logger()->newProgressOperation( transferJob, loggerText, transferJob, SLOT(slotCancel()) ); transferJob->start(); } void UmsCollectionLocation::removeUrlsFromCollection( const Meta::TrackList &sources ) { - KUrl::List sourceUrls; + QList sourceUrls; foreach( const Meta::TrackPtr track, sources ) { - KUrl trackUrl = track->playableUrl(); + QUrl trackUrl = track->playableUrl(); m_sourceUrlToTrackMap.insert( trackUrl, track ); sourceUrls.append( trackUrl ); } QString loggerText = i18np( "Removing one track from %2", "Removing %1 tracks from %2", sourceUrls.count(), m_umsCollection->prettyName() ); KIO::DeleteJob *delJob = KIO::del( sourceUrls, KIO::HideProgressInfo ); Amarok::Components::logger()->newProgressOperation( delJob, loggerText, delJob, SLOT(kill()) ); connect( delJob, SIGNAL(finished(KJob*)), SLOT(slotRemoveOperationFinished()) ); } void -UmsCollectionLocation::slotTrackTransferred( const KUrl &sourceTrackUrl ) +UmsCollectionLocation::slotTrackTransferred( const QUrl &sourceTrackUrl ) { Meta::TrackPtr sourceTrack = m_sourceUrlToTrackMap.value( sourceTrackUrl ); if( !sourceTrack ) warning() << __PRETTY_FUNCTION__ << ": I don't know about" << sourceTrackUrl; else // this is needed for example for "move" operation to actually remove source tracks source()->transferSuccessful( sourceTrack ); } void UmsCollectionLocation::slotRemoveOperationFinished() { foreach( Meta::TrackPtr track, m_sourceUrlToTrackMap ) { - KUrl trackUrl = track->playableUrl(); + QUrl trackUrl = track->playableUrl(); if( !trackUrl.isLocalFile() // just pretend it was deleted || !QFileInfo( trackUrl.toLocalFile() ).exists() ) { // good, the file was deleted. following is needed to trigger // CollectionLocation's functionality to remove empty dirs: transferSuccessful( track ); m_umsCollection->slotTrackRemoved( track ); } } CollectionLocation::slotRemoveOperationFinished(); } UmsTransferJob::UmsTransferJob( UmsCollectionLocation* location, const Transcoding::Configuration &configuration ) : KCompositeJob( location ) , m_location( location ) , m_transcodingConfiguration( configuration ) , m_abort( false ) { setCapabilities( KJob::Killable ); } void -UmsTransferJob::addCopy( const KUrl &from, const KUrl &to ) +UmsTransferJob::addCopy( const QUrl &from, const QUrl &to ) { m_copyList << KUrlPair( from, to ); } void -UmsTransferJob::addTranscode( const KUrl &from, const KUrl &to ) +UmsTransferJob::addTranscode( const QUrl &from, const QUrl &to ) { m_transcodeList << KUrlPair( from, to ); } void UmsTransferJob::start() { DEBUG_BLOCK; if( m_copyList.isEmpty() && m_transcodeList.isEmpty() ) return; m_totalTracks = m_transcodeList.size() + m_copyList.size(); startNextJob(); } void UmsTransferJob::slotCancel() { m_abort = true; } void UmsTransferJob::startNextJob() { if( m_abort ) { emitResult(); return; } KJob *job; if( !m_transcodeList.isEmpty() ) { KUrlPair urlPair = m_transcodeList.takeFirst(); job = new Transcoding::Job( urlPair.first, urlPair.second, m_transcodingConfiguration ); } else if( !m_copyList.isEmpty() ) { KUrlPair urlPair = m_copyList.takeFirst(); job = KIO::file_copy( urlPair.first, urlPair.second, -1, KIO::HideProgressInfo ); } else { emitResult(); return; } connect( job, SIGNAL(percent(KJob*,ulong)), SLOT(slotChildJobPercent(KJob*,ulong)) ); addSubjob( job ); job->start(); // no-op for KIO job, but matters for transcoding job } void UmsTransferJob::slotChildJobPercent( KJob *job, unsigned long percentage ) { Q_UNUSED(job) // the -1 is for the current track that is being processed but already removed from transferList int alreadyTransferred = m_totalTracks - m_transcodeList.size() - m_copyList.size() - 1; emitPercent( alreadyTransferred * 100.0 + percentage, 100.0 * m_totalTracks ); } void UmsTransferJob::slotResult( KJob *job ) { removeSubjob( job ); if( job->error() == KJob::NoError ) { KIO::FileCopyJob *copyJob = dynamic_cast( job ); Transcoding::Job *transcodingJob = dynamic_cast( job ); if( copyJob ) { emit sourceFileTransferDone( copyJob->srcUrl() ); emit fileTransferDone( copyJob->destUrl() ); } else if( transcodingJob ) { emit sourceFileTransferDone( transcodingJob->srcUrl() ); emit fileTransferDone( transcodingJob->destUrl() ); } else Debug::warning() << __PRETTY_FUNCTION__ << "invalid job passed to me!"; } else Debug::warning() << __PRETTY_FUNCTION__ << "job failed with" << job->error(); // transcoding job currently doesn't emit percentage, so emit it at least once for track emitPercent( m_totalTracks - ( m_transcodeList.size() + m_copyList.size() ), m_totalTracks ); startNextJob(); } diff --git a/src/core-impl/collections/umscollection/UmsCollectionLocation.h b/src/core-impl/collections/umscollection/UmsCollectionLocation.h index 6cfe4df27c..34a446d532 100644 --- a/src/core-impl/collections/umscollection/UmsCollectionLocation.h +++ b/src/core-impl/collections/umscollection/UmsCollectionLocation.h @@ -1,97 +1,97 @@ /**************************************************************************************** * Copyright (c) 2011 Bart Cerneels * * * * 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, see . * ****************************************************************************************/ #ifndef UMSCOLLECTIONLOCATION_H #define UMSCOLLECTIONLOCATION_H #include "core/collections/CollectionLocation.h" #include "UmsCollection.h" #include #include #include #include class UmsTransferJob; class UmsCollectionLocation : public Collections::CollectionLocation { Q_OBJECT public: UmsCollectionLocation( UmsCollection *umsCollection ); ~UmsCollectionLocation(); /* CollectionLocation methods */ virtual QString prettyLocation() const; virtual QStringList actualLocation() const; virtual bool isWritable() const; virtual bool isOrganizable() const; - virtual void copyUrlsToCollection( const QMap &sources, + virtual void copyUrlsToCollection( const QMap &sources, const Transcoding::Configuration &configuration ); virtual void removeUrlsFromCollection( const Meta::TrackList &sources ); protected slots: void slotRemoveOperationFinished(); // hides intentionally parent methods private slots: /** * Needed for removal of source tracks during move operation */ - void slotTrackTransferred( const KUrl &sourceTrackUrl ); + void slotTrackTransferred( const QUrl &sourceTrackUrl ); private: UmsCollection *m_umsCollection; - QHash m_sourceUrlToTrackMap; + QHash m_sourceUrlToTrackMap; }; class UmsTransferJob : public KCompositeJob { Q_OBJECT public: UmsTransferJob( UmsCollectionLocation *location, const Transcoding::Configuration &configuration ); - void addCopy( const KUrl &from, const KUrl &to ); - void addTranscode( const KUrl &from, const KUrl &to ); + void addCopy( const QUrl &from, const QUrl &to ); + void addTranscode( const QUrl &from, const QUrl &to ); virtual void start(); signals: - void sourceFileTransferDone( KUrl source ); - void fileTransferDone( KUrl destination ); + void sourceFileTransferDone( QUrl source ); + void fileTransferDone( QUrl destination ); public slots: void slotCancel(); private slots: void startNextJob(); void slotChildJobPercent( KJob *job, unsigned long percentage ); //reimplemented from KCompositeJob virtual void slotResult( KJob *job ); private: UmsCollectionLocation *m_location; Transcoding::Configuration m_transcodingConfiguration; bool m_abort; - typedef QPair KUrlPair; + typedef QPair KUrlPair; QList m_copyList; QList m_transcodeList; int m_totalTracks; // total number of tracks in whole transfer }; #endif // UMSCOLLECTIONLOCATION_H diff --git a/src/core-impl/collections/umscollection/podcasts/UmsPodcastMeta.cpp b/src/core-impl/collections/umscollection/podcasts/UmsPodcastMeta.cpp index 5c515fedd9..8d9aacae37 100644 --- a/src/core-impl/collections/umscollection/podcasts/UmsPodcastMeta.cpp +++ b/src/core-impl/collections/umscollection/podcasts/UmsPodcastMeta.cpp @@ -1,273 +1,273 @@ /**************************************************************************************** * Copyright (c) 2010 Bart Cerneels * * * * 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, see . * ****************************************************************************************/ #include "UmsPodcastMeta.h" #include "core/support/Debug.h" #include "core-impl/playlists/types/file/PlaylistFileSupport.h" #include "UmsPodcastProvider.h" using namespace Podcasts; UmsPodcastEpisodePtr UmsPodcastEpisode::fromPodcastEpisodePtr( PodcastEpisodePtr episode ) { return UmsPodcastEpisodePtr::dynamicCast( episode ); } UmsPodcastEpisodePtr UmsPodcastEpisode::fromTrackPtr( Meta::TrackPtr track ) { return UmsPodcastEpisodePtr::dynamicCast( track ); } PodcastEpisodePtr UmsPodcastEpisode::toPodcastEpisodePtr( UmsPodcastEpisodePtr episode ) { return PodcastEpisodePtr::dynamicCast( episode ); } PodcastEpisodeList UmsPodcastEpisode::toPodcastEpisodeList( UmsPodcastEpisodeList episodes ) { PodcastEpisodeList list; foreach( UmsPodcastEpisodePtr e, episodes ) list << toPodcastEpisodePtr( e ); return list; } UmsPodcastEpisode::UmsPodcastEpisode( UmsPodcastChannelPtr channel ) : Podcasts::PodcastEpisode( UmsPodcastChannel::toPodcastChannelPtr( channel ) ) { } UmsPodcastEpisode::~UmsPodcastEpisode() { } void -UmsPodcastEpisode::setLocalUrl( const KUrl &localUrl ) +UmsPodcastEpisode::setLocalUrl( const QUrl &localUrl ) { m_localUrl = localUrl; //TODO: load local file } -KUrl +QUrl UmsPodcastEpisode::playableUrl() const { if( m_localFile.isNull() ) return m_url; return m_localFile->playableUrl(); } QString UmsPodcastEpisode::notPlayableReason() const { if( m_localFile ) return m_localFile->notPlayableReason(); return PodcastEpisode::notPlayableReason(); } void UmsPodcastEpisode::setLocalFile( MetaFile::TrackPtr localFile ) { m_localFile = localFile; } QString UmsPodcastEpisode::title() const { if( m_localFile.isNull() ) return m_title; return m_localFile->name(); } QDateTime UmsPodcastEpisode::createDate() const { if( m_localFile ) return m_localFile->createDate(); return Meta::Track::createDate(); } void UmsPodcastEpisode::setTitle( const QString &title ) { if( !m_localFile.isNull() ) { m_localFile->setTitle( title ); } m_title = title; } Meta::AlbumPtr UmsPodcastEpisode::album() const { if( m_localFile.isNull() ) return m_albumPtr; return m_localFile->album(); } Meta::ArtistPtr UmsPodcastEpisode::artist() const { if( m_localFile.isNull() ) return m_artistPtr; return m_localFile->artist(); } Meta::ComposerPtr UmsPodcastEpisode::composer() const { if( m_localFile.isNull() ) return m_composerPtr; return m_localFile->composer(); } Meta::GenrePtr UmsPodcastEpisode::genre() const { if( m_localFile.isNull() ) return m_genrePtr; return m_localFile->genre(); } Meta::YearPtr UmsPodcastEpisode::year() const { if( m_localFile.isNull() ) return m_yearPtr; return m_localFile->year(); } UmsPodcastChannelPtr UmsPodcastChannel::fromPodcastChannelPtr( PodcastChannelPtr channel ) { return UmsPodcastChannelPtr::dynamicCast( channel ); } PodcastChannelPtr UmsPodcastChannel::toPodcastChannelPtr( UmsPodcastChannelPtr channel ) { return PodcastChannelPtr::dynamicCast( channel ); } PodcastChannelList UmsPodcastChannel::toPodcastChannelList( UmsPodcastChannelList umsChannels ) { PodcastChannelList channels; foreach( UmsPodcastChannelPtr umsChannel, umsChannels ) channels << UmsPodcastChannel::toPodcastChannelPtr( umsChannel ); return channels; } UmsPodcastChannel::UmsPodcastChannel( UmsPodcastProvider *provider ) : Podcasts::PodcastChannel() , m_provider( provider ) { } UmsPodcastChannel::UmsPodcastChannel( PodcastChannelPtr channel, UmsPodcastProvider *provider ) : Podcasts::PodcastChannel( channel ) , m_provider( provider ) { //Since we need to copy the tracks, make sure it's loaded. //TODO: we might also need to subscribe to get trackAdded() when channel does async loading. channel->triggerTrackLoad(); foreach( PodcastEpisodePtr episode, channel->episodes() ) addEpisode( episode ); } UmsPodcastChannel::~UmsPodcastChannel() { } PodcastEpisodePtr UmsPodcastChannel::addEpisode( PodcastEpisodePtr episode ) { DEBUG_BLOCK if( !episode->isNew() || !episode->playableUrl().isLocalFile() ) return PodcastEpisodePtr(); //we don't care about these. if( !m_provider ) return PodcastEpisodePtr(); return m_provider->addEpisode( episode ); //track adding is asynchronous, provider will call addUmsEpisode once done. //TODO: change this so track can show progress once playlist-inline-progress is implemented } void UmsPodcastChannel::addUmsEpisode( UmsPodcastEpisodePtr umsEpisode ) { int i = 0; foreach( UmsPodcastEpisodePtr e, m_umsEpisodes ) { if( umsEpisode->createDate() > e->createDate() ) { i = m_umsEpisodes.indexOf( e ); break; } } m_umsEpisodes.insert( i, umsEpisode ); notifyObserversTrackAdded( Meta::TrackPtr::dynamicCast( umsEpisode ), i ); } void -UmsPodcastChannel::setPlaylistFileSource( const KUrl &playlistFilePath ) +UmsPodcastChannel::setPlaylistFileSource( const QUrl &playlistFilePath ) { m_playlistFilePath = playlistFilePath; m_playlistFile = Playlists::loadPlaylistFile( playlistFilePath ); //now parse the playlist and use it to create out episode list } Playlists::PlaylistProvider * UmsPodcastChannel::provider() const { return dynamic_cast( m_provider ); } void UmsPodcastChannel::removeEpisode( UmsPodcastEpisodePtr episode ) { int position = m_umsEpisodes.indexOf( episode ); if( position == -1 ) { error() << title() << " does't have this episode"; return; } m_umsEpisodes.removeAt( position ); notifyObserversTrackRemoved( position ); } diff --git a/src/core-impl/collections/umscollection/podcasts/UmsPodcastMeta.h b/src/core-impl/collections/umscollection/podcasts/UmsPodcastMeta.h index 3b22df338f..115be0e494 100644 --- a/src/core-impl/collections/umscollection/podcasts/UmsPodcastMeta.h +++ b/src/core-impl/collections/umscollection/podcasts/UmsPodcastMeta.h @@ -1,122 +1,122 @@ /**************************************************************************************** * Copyright (c) 2010 Bart Cerneels * * * * 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, see . * ****************************************************************************************/ #ifndef UMSPODCASTMETA_H #define UMSPODCASTMETA_H #include "core-impl/playlists/types/file/PlaylistFile.h" #include "core/podcasts/PodcastMeta.h" #include "core-impl/meta/file/File.h" -class KUrl; +class QUrl; namespace Podcasts { class UmsPodcastEpisode; class UmsPodcastChannel; class UmsPodcastProvider; typedef KSharedPtr UmsPodcastEpisodePtr; typedef KSharedPtr UmsPodcastChannelPtr; typedef QList UmsPodcastEpisodeList; typedef QList UmsPodcastChannelList; class UmsPodcastEpisode : public Podcasts::PodcastEpisode { friend class UmsPodcastProvider; public: static UmsPodcastEpisodePtr fromPodcastEpisodePtr( Podcasts::PodcastEpisodePtr episode ); static UmsPodcastEpisodePtr fromTrackPtr( Meta::TrackPtr track ); static Podcasts::PodcastEpisodePtr toPodcastEpisodePtr( UmsPodcastEpisodePtr episode ); static Podcasts::PodcastEpisodeList toPodcastEpisodeList( UmsPodcastEpisodeList episodes ); UmsPodcastEpisode( UmsPodcastChannelPtr channel ); ~UmsPodcastEpisode(); void setLocalFile( MetaFile::TrackPtr localFile ); //PodcastEpisode methods virtual QString title() const; - virtual void setLocalUrl( const KUrl &localUrl ); + virtual void setLocalUrl( const QUrl &localUrl ); //Track Methods virtual QString name() const { return title(); } - virtual KUrl playableUrl() const; + virtual QUrl playableUrl() const; virtual QString notPlayableReason() const; virtual QString prettyName() const { return name(); } virtual void setTitle( const QString &title ); virtual QDateTime createDate() const; virtual Meta::AlbumPtr album() const; virtual Meta::ArtistPtr artist() const; virtual Meta::ComposerPtr composer() const; virtual Meta::GenrePtr genre() const; virtual Meta::YearPtr year() const; private: MetaFile::TrackPtr m_localFile; UmsPodcastChannelPtr m_umsChannel; }; class UmsPodcastChannel : public Podcasts::PodcastChannel { friend class UmsPodcastProvider; public: static UmsPodcastChannelPtr fromPodcastChannelPtr( Podcasts::PodcastChannelPtr channel ); static Podcasts::PodcastChannelPtr toPodcastChannelPtr( UmsPodcastChannelPtr channel ); static Podcasts::PodcastChannelList toPodcastChannelList( UmsPodcastChannelList umsChannels ); UmsPodcastChannel( UmsPodcastProvider *provider ); UmsPodcastChannel( Podcasts::PodcastChannelPtr channel, UmsPodcastProvider *provider ); ~UmsPodcastChannel(); virtual Podcasts::PodcastEpisodePtr addEpisode( Podcasts::PodcastEpisodePtr episode ); UmsPodcastEpisodeList umsEpisodes() { return m_umsEpisodes; } void addUmsEpisode( UmsPodcastEpisodePtr episode ); - void setPlaylistFileSource( const KUrl &playlistFilePath ); - KUrl playlistFilePath() const { return m_playlistFilePath; } + void setPlaylistFileSource( const QUrl &playlistFilePath ); + QUrl playlistFilePath() const { return m_playlistFilePath; } virtual Podcasts::PodcastEpisodeList episodes() const { return UmsPodcastEpisode::toPodcastEpisodeList( m_umsEpisodes ); } virtual Playlists::PlaylistProvider *provider() const; protected: void removeEpisode( UmsPodcastEpisodePtr episode ); private: UmsPodcastProvider *m_provider; - KUrl m_playlistFilePath; + QUrl m_playlistFilePath; Playlists::PlaylistFilePtr m_playlistFile; //used to keep track of episodes. UmsPodcastEpisodeList m_umsEpisodes; }; } //namespace Podcasts Q_DECLARE_METATYPE( Podcasts::UmsPodcastEpisodePtr ) Q_DECLARE_METATYPE( Podcasts::UmsPodcastEpisodeList ) Q_DECLARE_METATYPE( Podcasts::UmsPodcastChannelPtr ) Q_DECLARE_METATYPE( Podcasts::UmsPodcastChannelList ) #endif // UMSPODCASTMETA_H diff --git a/src/core-impl/collections/umscollection/podcasts/UmsPodcastProvider.cpp b/src/core-impl/collections/umscollection/podcasts/UmsPodcastProvider.cpp index 6f90ab2ab0..128287221f 100644 --- a/src/core-impl/collections/umscollection/podcasts/UmsPodcastProvider.cpp +++ b/src/core-impl/collections/umscollection/podcasts/UmsPodcastProvider.cpp @@ -1,556 +1,558 @@ /**************************************************************************************** * Copyright (c) 2010 Bart Cerneels * * * * 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, see . * ****************************************************************************************/ #include "UmsPodcastProvider.h" #include "core/support/Debug.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace Podcasts; -UmsPodcastProvider::UmsPodcastProvider( KUrl scanDirectory ) +UmsPodcastProvider::UmsPodcastProvider( QUrl scanDirectory ) : m_scanDirectory( scanDirectory ) , m_deleteEpisodeAction( 0 ) , m_deleteChannelAction( 0 ) { } UmsPodcastProvider::~UmsPodcastProvider() { } bool -UmsPodcastProvider::possiblyContainsTrack( const KUrl &url ) const +UmsPodcastProvider::possiblyContainsTrack( const QUrl &url ) const { Q_UNUSED( url ) return false; } Meta::TrackPtr -UmsPodcastProvider::trackForUrl( const KUrl &url ) +UmsPodcastProvider::trackForUrl( const QUrl &url ) { Q_UNUSED( url ) return Meta::TrackPtr(); } PodcastEpisodePtr UmsPodcastProvider::episodeForGuid( const QString &guid ) { Q_UNUSED( guid ) return PodcastEpisodePtr(); } void -UmsPodcastProvider::addPodcast( const KUrl &url ) +UmsPodcastProvider::addPodcast( const QUrl &url ) { Q_UNUSED( url ); } PodcastChannelPtr UmsPodcastProvider::addChannel( PodcastChannelPtr channel ) { UmsPodcastChannelPtr umsChannel = UmsPodcastChannelPtr( new UmsPodcastChannel( channel, this ) ); m_umsChannels << umsChannel; emit playlistAdded( Playlists::PlaylistPtr( umsChannel.data() ) ); return PodcastChannelPtr( umsChannel.data() ); } PodcastEpisodePtr UmsPodcastProvider::addEpisode( PodcastEpisodePtr episode ) { - KUrl localFilePath = episode->playableUrl(); + QUrl localFilePath = episode->playableUrl(); if( !localFilePath.isLocalFile() ) return PodcastEpisodePtr(); - KUrl destination = KUrl( m_scanDirectory ); - destination.addPath( Amarok::vfatPath( episode->channel()->prettyName() ) ); + QUrl destination = QUrl( m_scanDirectory ); + destination = destination.adjusted(QUrl::StripTrailingSlash); + destination.setPath(destination.path() + '/' + ( Amarok::vfatPath( episode->channel()->prettyName() ) )); KIO::mkdir( destination ); - destination.addPath( Amarok::vfatPath( localFilePath.fileName() ) ); + destination = destination.adjusted(QUrl::StripTrailingSlash); + destination.setPath(destination.path() + '/' + ( Amarok::vfatPath( localFilePath.fileName() ) )); debug() << QString( "Copy episode \"%1\" to %2" ).arg( localFilePath.path()) .arg( destination.path() ); KIO::FileCopyJob *copyJob = KIO::file_copy( localFilePath, destination ); connect( copyJob, SIGNAL(result(KJob*)), SLOT(slotCopyComplete(KJob*)) ); copyJob->start(); //we have not copied the data over yet so we can't return an episode yet //TODO: return a proxy for the episode we are still copying. return PodcastEpisodePtr(); } void UmsPodcastProvider::slotCopyComplete( KJob *job ) { KIO::FileCopyJob *copyJob = dynamic_cast( job ); if( !copyJob ) return; - KUrl localFilePath = copyJob->destUrl(); + QUrl localFilePath = copyJob->destUrl(); MetaFile::Track *fileTrack = new MetaFile::Track( localFilePath ); UmsPodcastEpisodePtr umsEpisode = addFile( MetaFile::TrackPtr( fileTrack ) ); } PodcastChannelList UmsPodcastProvider::channels() { return UmsPodcastChannel::toPodcastChannelList( m_umsChannels ); } void UmsPodcastProvider::removeSubscription( PodcastChannelPtr channel ) { UmsPodcastChannelPtr umsChannel = UmsPodcastChannelPtr::dynamicCast( channel ); if( umsChannel.isNull() ) { error() << "trying to remove a podcast channel of the wrong type"; return; } if( !m_umsChannels.contains( umsChannel ) ) { error() << "trying to remove a podcast channel that is not in the list"; return; } m_umsChannels.removeAll( umsChannel ); } void UmsPodcastProvider::configureProvider() { } void UmsPodcastProvider::configureChannel( PodcastChannelPtr channel ) { Q_UNUSED( channel ); } QString UmsPodcastProvider::prettyName() const { return i18nc( "Podcasts on a media device", "Podcasts on %1", QString("TODO: replace me") ); } KIcon UmsPodcastProvider::icon() const { return KIcon("drive-removable-media-usb-pendrive"); } Playlists::PlaylistList UmsPodcastProvider::playlists() { Playlists::PlaylistList playlists; foreach( UmsPodcastChannelPtr channel, m_umsChannels ) playlists << Playlists::PlaylistPtr::dynamicCast( channel ); return playlists; } QActionList UmsPodcastProvider::episodeActions( PodcastEpisodeList episodes ) { QActionList actions; if( episodes.isEmpty() ) return actions; if( m_deleteEpisodeAction == 0 ) { m_deleteEpisodeAction = new QAction( KIcon( "edit-delete" ), i18n( "&Delete Episode" ), this ); m_deleteEpisodeAction->setProperty( "popupdropper_svg_id", "delete" ); connect( m_deleteEpisodeAction, SIGNAL(triggered()), SLOT(slotDeleteEpisodes()) ); } // set the episode list as data that we'll retrieve in the slot m_deleteEpisodeAction->setData( QVariant::fromValue( episodes ) ); actions << m_deleteEpisodeAction; return actions; } void UmsPodcastProvider::slotDeleteEpisodes() { DEBUG_BLOCK QAction *action = qobject_cast( QObject::sender() ); if( action == 0 ) return; //get the list of episodes to apply to, then clear that data. PodcastEpisodeList episodes = action->data().value(); action->setData( QVariant() ); UmsPodcastEpisodeList umsEpisodes; foreach( PodcastEpisodePtr episode, episodes ) { UmsPodcastEpisodePtr umsEpisode = UmsPodcastEpisode::fromPodcastEpisodePtr( episode ); if( !umsEpisode ) { error() << "Could not cast to UmsPodcastEpisode"; continue; } PodcastChannelPtr channel = umsEpisode->channel(); if( !channel ) { error() << "episode did not have a valid channel"; continue; } UmsPodcastChannelPtr umsChannel = UmsPodcastChannel::fromPodcastChannelPtr( channel ); if( !umsChannel ) { error() << "Could not cast to UmsPodcastChannel"; continue; } umsEpisodes << umsEpisode; } deleteEpisodes( umsEpisodes ); } void UmsPodcastProvider::deleteEpisodes( UmsPodcastEpisodeList umsEpisodes ) { - KUrl::List urlsToDelete; + QList urlsToDelete; foreach( UmsPodcastEpisodePtr umsEpisode, umsEpisodes ) urlsToDelete << umsEpisode->playableUrl(); KDialog dialog; dialog.setCaption( i18n( "Confirm Delete" ) ); dialog.setButtons( KDialog::Ok | KDialog::Cancel ); QLabel label( i18np( "Are you sure you want to delete this episode?", "Are you sure you want to delete these %1 episodes?", urlsToDelete.count() ) , &dialog ); QListWidget listWidget( &dialog ); listWidget.setSelectionMode( QAbstractItemView::NoSelection ); - foreach( const KUrl &url, urlsToDelete ) + foreach( const QUrl &url, urlsToDelete ) { new QListWidgetItem( url.toLocalFile(), &listWidget ); } QWidget *widget = new QWidget( &dialog ); QVBoxLayout *layout = new QVBoxLayout( widget ); layout->addWidget( &label ); layout->addWidget( &listWidget ); dialog.setButtonText( KDialog::Ok, i18n( "Yes, delete from %1.", QString("TODO: replace me") ) ); dialog.setMainWidget( widget ); if( dialog.exec() != QDialog::Accepted ) return; KIO::DeleteJob *deleteJob = KIO::del( urlsToDelete, KIO::HideProgressInfo ); //keep track of these episodes until the job is done m_deleteJobMap.insert( deleteJob, umsEpisodes ); connect( deleteJob, SIGNAL(result(KJob*)), SLOT(deleteJobComplete(KJob*)) ); } void UmsPodcastProvider::deleteJobComplete( KJob *job ) { DEBUG_BLOCK if( job->error() ) { error() << "problem deleting episode(s): " << job->errorString(); return; } UmsPodcastEpisodeList deletedEpisodes = m_deleteJobMap.take( job ); foreach( UmsPodcastEpisodePtr deletedEpisode, deletedEpisodes ) { PodcastChannelPtr channel = deletedEpisode->channel(); UmsPodcastChannelPtr umsChannel = UmsPodcastChannel::fromPodcastChannelPtr( channel ); if( !umsChannel ) { error() << "Could not cast to UmsPodcastChannel"; continue; } umsChannel->removeEpisode( deletedEpisode ); if( umsChannel->m_umsEpisodes.isEmpty() ) { debug() << "channel is empty now, remove it"; m_umsChannels.removeAll( umsChannel ); emit( playlistRemoved( Playlists::PlaylistPtr::dynamicCast( umsChannel ) ) ); } } } QActionList UmsPodcastProvider::channelActions( PodcastChannelList channels ) { QActionList actions; if( channels.isEmpty() ) return actions; if( m_deleteChannelAction == 0 ) { m_deleteChannelAction = new QAction( KIcon( "edit-delete" ), i18n( "&Delete " "Channel and Episodes" ), this ); m_deleteChannelAction->setProperty( "popupdropper_svg_id", "delete" ); connect( m_deleteChannelAction, SIGNAL(triggered()), SLOT(slotDeleteChannels()) ); } // set the episode list as data that we'll retrieve in the slot m_deleteChannelAction->setData( QVariant::fromValue( channels ) ); actions << m_deleteChannelAction; return actions; } void UmsPodcastProvider::slotDeleteChannels() { DEBUG_BLOCK QAction *action = qobject_cast( QObject::sender() ); if( action == 0 ) return; //get the list of episodes to apply to, then clear that data. PodcastChannelList channels = action->data().value(); action->setData( QVariant() ); foreach( PodcastChannelPtr channel, channels ) { UmsPodcastChannelPtr umsChannel = UmsPodcastChannel::fromPodcastChannelPtr( channel ); if( !umsChannel ) { error() << "Could not cast to UmsPodcastChannel"; continue; } deleteEpisodes( umsChannel->m_umsEpisodes ); //slot deleteJobComplete() will emit signal once all tracks are gone. } } QActionList UmsPodcastProvider::playlistActions( const Playlists::PlaylistList &playlists ) { PodcastChannelList channels; foreach( const Playlists::PlaylistPtr &playlist, playlists ) { PodcastChannelPtr channel = PodcastChannelPtr::dynamicCast( playlist ); if( channel ) channels << channel; } return channelActions( channels ); } QActionList UmsPodcastProvider::trackActions( const QMultiHash &playlistTracks ) { PodcastEpisodeList episodes; foreach( const Playlists::PlaylistPtr &playlist, playlistTracks.uniqueKeys() ) { PodcastChannelPtr channel = PodcastChannelPtr::dynamicCast( playlist ); if( !channel ) continue; PodcastEpisodeList channelEpisodes = channel->episodes(); QList trackPositions = playlistTracks.values( playlist ); qSort( trackPositions ); foreach( int trackPosition, trackPositions ) { if( trackPosition >= 0 && trackPosition < channelEpisodes.count() ) episodes << channelEpisodes.at( trackPosition ); } } return episodeActions( episodes ); } void UmsPodcastProvider::completePodcastDownloads() { } void UmsPodcastProvider::updateAll() //slot { } void UmsPodcastProvider::update( Podcasts::PodcastChannelPtr channel ) //slot { Q_UNUSED( channel ); } void UmsPodcastProvider::downloadEpisode( Podcasts::PodcastEpisodePtr episode ) //slot { Q_UNUSED( episode ); } void UmsPodcastProvider::deleteDownloadedEpisode( Podcasts::PodcastEpisodePtr episode ) //slot { Q_UNUSED( episode ); } void UmsPodcastProvider::slotUpdated() //slot { } void UmsPodcastProvider::scan() { if( m_scanDirectory.isEmpty() ) return; m_dirList.clear(); debug() << "scan directory for podcasts: " << - m_scanDirectory.toLocalFile( KUrl::AddTrailingSlash ); + m_scanDirectory.toLocalFile( QUrl::AddTrailingSlash ); QDirIterator it( m_scanDirectory.toLocalFile(), QDirIterator::Subdirectories ); while( it.hasNext() ) addPath( it.next() ); } int UmsPodcastProvider::addPath( const QString &path ) { DEBUG_BLOCK int acc = 0; debug() << path; QMimeType mime = KMimeType::findByFileContent( path, &acc ); if( !mime || mime.name() == KMimeType::defaultMimeType() ) { debug() << "Trying again with findByPath:" ; mime = db.mimeTypeForFile( path, 0, true, &acc ); if( mime.name() == KMimeType::defaultMimeType() ) return 0; } debug() << "Got type: " << mime.name() << ", with accuracy: " << acc; QFileInfo info( path ); if( info.isDir() ) { if( m_dirList.contains( path ) ) return 0; m_dirList << info.canonicalPath(); return 1; } else if( info.isFile() ) { // foreach( const QString &mimetype, m_handler->mimetypes() ) // { // if( mime.inherits( mimetype ) ) // { addFile( MetaFile::TrackPtr( new MetaFile::Track( - KUrl( info.canonicalFilePath() ) ) ) ); + QUrl( info.canonicalFilePath() ) ) ) ); return 2; // } // } } return 0; } UmsPodcastEpisodePtr UmsPodcastProvider::addFile( MetaFile::TrackPtr metafileTrack ) { DEBUG_BLOCK debug() << metafileTrack->playableUrl().url(); debug() << "album: " << metafileTrack->album()->name(); debug() << "title: " << metafileTrack->name(); if( metafileTrack->album()->name().isEmpty() ) { debug() << "Can't figure out channel without album tag."; return UmsPodcastEpisodePtr(); } if( metafileTrack->name().isEmpty() ) { debug() << "Can not use a track without a title."; return UmsPodcastEpisodePtr(); } //see if there is already a UmsPodcastEpisode for this track UmsPodcastChannelPtr channel; UmsPodcastEpisodePtr episode; foreach( UmsPodcastChannelPtr c, m_umsChannels ) { if( c->name() == metafileTrack->album()->name() ) { channel = c; break; } } if( channel ) { foreach( UmsPodcastEpisodePtr e, channel->umsEpisodes() ) { if( e->title() == metafileTrack->name() ) { episode = e; break; } } } else { debug() << "there is no channel for this episode yet"; channel = UmsPodcastChannelPtr( new UmsPodcastChannel( this ) ); channel->setTitle( metafileTrack->album()->name() ); m_umsChannels << channel; emit playlistAdded( Playlists::PlaylistPtr( channel.data() ) ); } if( episode.isNull() ) { debug() << "this episode was not found in an existing channel"; episode = UmsPodcastEpisodePtr( new UmsPodcastEpisode( channel ) ); episode->setLocalFile( metafileTrack ); channel->addUmsEpisode( episode ); } episode->setLocalFile( metafileTrack ); return episode; } diff --git a/src/core-impl/collections/umscollection/podcasts/UmsPodcastProvider.h b/src/core-impl/collections/umscollection/podcasts/UmsPodcastProvider.h index 18f29f6885..a9b53a5cea 100644 --- a/src/core-impl/collections/umscollection/podcasts/UmsPodcastProvider.h +++ b/src/core-impl/collections/umscollection/podcasts/UmsPodcastProvider.h @@ -1,101 +1,101 @@ /**************************************************************************************** * Copyright (c) 2010 Bart Cerneels * * * * 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, see . * ****************************************************************************************/ #ifndef UMSPODCASTPROVIDER_H #define UMSPODCASTPROVIDER_H #include "core/podcasts/PodcastProvider.h" #include "UmsPodcastMeta.h" class KJob; namespace Podcasts { class UmsPodcastProvider : public PodcastProvider { Q_OBJECT public: - UmsPodcastProvider( KUrl scanDirectory ); + UmsPodcastProvider( QUrl scanDirectory ); ~UmsPodcastProvider(); UmsPodcastEpisodePtr addFile( MetaFile::TrackPtr metafileTrack ); int addPath( const QString &path ); - virtual bool possiblyContainsTrack( const KUrl &url ) const; - virtual Meta::TrackPtr trackForUrl( const KUrl &url ); + virtual bool possiblyContainsTrack( const QUrl &url ) const; + virtual Meta::TrackPtr trackForUrl( const QUrl &url ); virtual Podcasts::PodcastEpisodePtr episodeForGuid( const QString &guid ); - virtual void addPodcast( const KUrl &url ); + virtual void addPodcast( const QUrl &url ); virtual Podcasts::PodcastChannelPtr addChannel( Podcasts::PodcastChannelPtr channel ); virtual Podcasts::PodcastEpisodePtr addEpisode( Podcasts::PodcastEpisodePtr episode ); virtual Podcasts::PodcastChannelList channels(); virtual void removeSubscription( Podcasts::PodcastChannelPtr channel ); virtual void configureProvider(); virtual void configureChannel( Podcasts::PodcastChannelPtr channel ); // PlaylistProvider methods virtual QString prettyName() const; virtual KIcon icon() const; virtual Playlists::PlaylistList playlists(); virtual QActionList playlistActions( const Playlists::PlaylistList &playlists ); virtual QActionList trackActions( const QMultiHash &playlistTracks ); virtual void completePodcastDownloads(); public slots: virtual void updateAll(); virtual void update( Podcasts::PodcastChannelPtr channel ); virtual void downloadEpisode( Podcasts::PodcastEpisodePtr episode ); virtual void deleteDownloadedEpisode( Podcasts::PodcastEpisodePtr episode ); virtual void slotUpdated(); virtual void scan(); signals: void updated(); private slots: void slotDeleteEpisodes(); void slotDeleteChannels(); void deleteJobComplete( KJob *job ); void slotCopyComplete( KJob *job ); private: QList episodeActions( Podcasts::PodcastEpisodeList ); QList channelActions( Podcasts::PodcastChannelList ); void deleteEpisodes( UmsPodcastEpisodeList umsEpisodes ); - KUrl m_scanDirectory; + QUrl m_scanDirectory; QStringList m_dirList; UmsPodcastChannelList m_umsChannels; QAction *m_deleteEpisodeAction; //delete a downloaded Episode QAction *m_deleteChannelAction; //delete a everything from one channel QList m_providerActions; QMap m_deleteJobMap; }; } //namespace Podcasts #endif // UMSPODCASTPROVIDER_H diff --git a/src/core-impl/collections/upnpcollection/UpnpBrowseCollection.cpp b/src/core-impl/collections/upnpcollection/UpnpBrowseCollection.cpp index 83a09afcc7..fb6b114834 100644 --- a/src/core-impl/collections/upnpcollection/UpnpBrowseCollection.cpp +++ b/src/core-impl/collections/upnpcollection/UpnpBrowseCollection.cpp @@ -1,289 +1,289 @@ /**************************************************************************************** * Copyright (c) 2010 Nikhil Marathe * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "UpnpBrowseCollection" #include "UpnpBrowseCollection.h" #include "core/interfaces/Logger.h" #include "core/support/Components.h" #include "core/support/Debug.h" #include "MemoryQueryMaker.h" #include "UpnpMemoryQueryMaker.h" #include "UpnpQueryMaker.h" #include "UpnpMeta.h" #include "UpnpCache.h" #include #include #include #include #include "upnptypes.h" #include #include using namespace Meta; namespace Collections { //UpnpBrowseCollection // TODO register for the device bye bye and emit remove() UpnpBrowseCollection::UpnpBrowseCollection( const DeviceInfo& dev ) : UpnpCollectionBase( dev ) , m_mc( new MemoryCollection() ) , m_fullScanInProgress( false ) , m_cache( new UpnpCache( this ) ) { DEBUG_BLOCK // experimental code, will probably be moved to a better place OrgKdeKDirNotifyInterface *notify = new OrgKdeKDirNotifyInterface("", "", QDBusConnection::sessionBus(), this ); connect( notify, SIGNAL(FilesChanged(QStringList)), SLOT(slotFilesChanged(QStringList)) ); } UpnpBrowseCollection::~UpnpBrowseCollection() { } void UpnpBrowseCollection::slotFilesChanged(const QStringList &list ) { if( m_fullScanInProgress ) return; m_updateQueue += list; debug() << "Files changed" << list; } void UpnpBrowseCollection::processUpdates() { if( m_updateQueue.isEmpty() ) return; QString urlString = m_updateQueue.dequeue(); debug() << "Update URL is" << urlString; invalidateTracksIn( urlString ); - KUrl url( urlString ); + QUrl url( urlString ); if( url.scheme() != "upnp-ms" || m_device.uuid() != url.host() ) return; debug() << "Now incremental scanning" << url; startIncrementalScan( url.path() ); } void UpnpBrowseCollection::invalidateTracksIn( const QString &dir ) { debug() << "INVALIDATING" << m_tracksInContainer[dir].length(); /* * when we get dir as / a / b we also have to invalidate * any tracks in / a / b / * so we need to iterate over keys * If performance is really affected we can use some * kind of a prefix tree instead of a hash. */ foreach( const QString &key, m_tracksInContainer.keys() ) { if( key.startsWith( dir ) ) { debug() << key << " matches " << dir; foreach( TrackPtr track, m_tracksInContainer[dir] ) { removeTrack( track ); } } } m_tracksInContainer.remove( dir ); } void UpnpBrowseCollection::startFullScan() { DEBUG_BLOCK; // TODO probably set abort slot // TODO figure out what to do with the total steps Amarok::Components::logger()->newProgressOperation( this, i18n( "Scanning %1", prettyName() ) ); startIncrementalScan( "/" ); m_fullScanInProgress = true; m_fullScanTimer = new QTimer( this ); Q_ASSERT( connect( m_fullScanTimer, SIGNAL(timeout()), this, SLOT(updateMemoryCollection()) ) ); m_fullScanTimer->start(5000); } void UpnpBrowseCollection::startIncrementalScan( const QString &directory ) { if( m_fullScanInProgress ) { debug() << "Full scan in progress, aborting"; return; } debug() << "Scanning directory" << directory; - KUrl url; + QUrl url; url.setScheme( "upnp-ms" ); url.setHost( m_device.uuid() ); url.setPath( directory ); KIO::ListJob *listJob = KIO::listRecursive( url, KIO::HideProgressInfo ); addJob( listJob ); Q_ASSERT( connect( listJob, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), this, SLOT(entries(KIO::Job*,KIO::UDSEntryList)), Qt::UniqueConnection ) ); Q_ASSERT( connect( listJob, SIGNAL(result(KJob*)), this, SLOT(done(KJob*)), Qt::UniqueConnection ) ); listJob->start(); } void UpnpBrowseCollection::entries( KIO::Job *job, const KIO::UDSEntryList &list ) { DEBUG_BLOCK; int count = 0; KIO::SimpleJob *sj = static_cast( job ); foreach( const KIO::UDSEntry &entry, list ) { if( entry.contains( KIO::UPNP_CLASS ) && entry.stringValue( KIO::UPNP_CLASS ).startsWith( "object.item.audioItem" ) ) { createTrack( entry, sj->url().prettyUrl() ); } count++; emit totalSteps( count ); emit incrementProgress(); } updateMemoryCollection(); } void UpnpBrowseCollection::updateMemoryCollection() { memoryCollection()->setTrackMap( m_cache->tracks() ); memoryCollection()->setArtistMap( m_cache->artists() ); memoryCollection()->setAlbumMap( m_cache->albums() ); memoryCollection()->setGenreMap( m_cache->genres() ); memoryCollection()->setYearMap( m_cache->years() ); emit updated(); } void UpnpBrowseCollection::createTrack( const KIO::UDSEntry &entry, const QString &baseUrl ) { DEBUG_BLOCK TrackPtr t = m_cache->getTrack( entry ); QFileInfo info( entry.stringValue( KIO::UDSEntry::UDS_NAME ) ); QString container = QDir(baseUrl).filePath( info.dir().path() ); debug() << "CONTAINER" << container; m_tracksInContainer[container] << t; } void UpnpBrowseCollection::removeTrack( TrackPtr t ) { m_cache->removeTrack( t ); } void UpnpBrowseCollection::done( KJob *job ) { DEBUG_BLOCK if( job->error() ) { Amarok::Components::logger()->longMessage( i18n("UPnP Error: %1", job->errorString() ), Amarok::Logger::Error ); return; } updateMemoryCollection(); if( m_fullScanInProgress ) { m_fullScanTimer->stop(); m_fullScanInProgress = false; emit endProgressOperation( this ); debug() << "Full Scan done"; } // process new updates if any // this is the only place processUpdates() // should be called since a full scan at the very beginning // will always call done(). processUpdates(); } bool UpnpBrowseCollection::hasCapabilityInterface( Capabilities::Capability::Type type ) const { return ( type == Capabilities::Capability::CollectionScan ); } Capabilities::Capability* UpnpBrowseCollection::createCapabilityInterface( Capabilities::Capability::Type type ) { if( type == Capabilities::Capability::CollectionScan ) return new UpnpBrowseCollectionScanCapability( this ); else return 0; } QueryMaker* UpnpBrowseCollection::queryMaker() { DEBUG_BLOCK; UpnpMemoryQueryMaker *umqm = new UpnpMemoryQueryMaker(m_mc.toWeakRef(), collectionId() ); Q_ASSERT( connect( umqm, SIGNAL(startFullScan()), this, SLOT(startFullScan()) ) ); return umqm; } Meta::TrackPtr -UpnpBrowseCollection::trackForUrl( const KUrl &url ) +UpnpBrowseCollection::trackForUrl( const QUrl &url ) { debug() << "TRACK FOR URL " << url; if( url.scheme() == "upnptrack" && url.host() == collectionId() ) return m_cache->tracks()[url.url()]; debug() << "NONE FOUND"; return Collection::trackForUrl( url ); } // ---------- CollectionScanCapability ------------ UpnpBrowseCollectionScanCapability::UpnpBrowseCollectionScanCapability( UpnpBrowseCollection* collection ) : m_collection( collection ) { } UpnpBrowseCollectionScanCapability::~UpnpBrowseCollectionScanCapability() { } void UpnpBrowseCollectionScanCapability::startFullScan() { m_collection->startFullScan(); } void UpnpBrowseCollectionScanCapability::startIncrementalScan( const QString &directory ) { m_collection->startIncrementalScan( directory ); } void UpnpBrowseCollectionScanCapability::stopScan() { // the UpnpBrowseCollection does not yet know how to stop a scan } } //~ namespace diff --git a/src/core-impl/collections/upnpcollection/UpnpBrowseCollection.h b/src/core-impl/collections/upnpcollection/UpnpBrowseCollection.h index 45035fb88c..1422126d52 100644 --- a/src/core-impl/collections/upnpcollection/UpnpBrowseCollection.h +++ b/src/core-impl/collections/upnpcollection/UpnpBrowseCollection.h @@ -1,120 +1,120 @@ /**************************************************************************************** * Copyright (c) 2010 Nikhil Marathe * * * * 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, see . * ****************************************************************************************/ #ifndef UPNPBROWSECOLLECTION_H #define UPNPBROWSECOLLECTION_H #include "UpnpCollectionBase.h" #include "MemoryCollection.h" #include "core/capabilities/CollectionScanCapability.h" #include #include #include #include #include #include #include #include #include namespace KIO { class Job; class ListJob; } class KJob; class QTimer; namespace Collections { class UpnpCache; class UpnpMemoryQueryMaker; class UpnpBrowseCollection : public UpnpCollectionBase { Q_OBJECT public: UpnpBrowseCollection( const DeviceInfo& ); virtual ~UpnpBrowseCollection(); virtual QueryMaker* queryMaker(); virtual bool hasCapabilityInterface( Capabilities::Capability::Type type ) const; virtual Capabilities::Capability* createCapabilityInterface( Capabilities::Capability::Type type ); - Meta::TrackPtr trackForUrl( const KUrl &url ); + Meta::TrackPtr trackForUrl( const QUrl &url ); virtual KIcon icon() const { return KIcon("network-server"); } QSharedPointer memoryCollection() const { return m_mc; } signals: void incrementProgress(); void totalSteps( int ); void endProgressOperation( QObject * ); public slots: virtual void startFullScan(); virtual void startIncrementalScan( const QString &directory = QString() ); private slots: void entries( KIO::Job *, const KIO::UDSEntryList& ); void done( KJob * ); void createTrack( const KIO::UDSEntry &, const QString &baseUrl ); void removeTrack( Meta::TrackPtr t ); void invalidateTracksIn( const QString &dir ); void updateMemoryCollection(); void slotFilesChanged(const QStringList &); void processUpdates(); private: QSharedPointer m_mc; QTimer *m_fullScanTimer; bool m_fullScanInProgress; // associates each track with its UPNP Parent // when a update occurs on a // invalidate all tracks, and rescan // it remains to be seen how badly this // affects performance or memory QHash m_tracksInContainer; QQueue m_updateQueue; UpnpCache *m_cache; }; class UpnpBrowseCollectionScanCapability : public Capabilities::CollectionScanCapability { Q_OBJECT public: UpnpBrowseCollectionScanCapability( UpnpBrowseCollection* collection ); virtual ~UpnpBrowseCollectionScanCapability(); virtual void startFullScan(); virtual void startIncrementalScan( const QString &directory = QString() ); virtual void stopScan(); private: UpnpBrowseCollection *m_collection; }; } //namespace Collections #endif diff --git a/src/core-impl/collections/upnpcollection/UpnpCollectionBase.cpp b/src/core-impl/collections/upnpcollection/UpnpCollectionBase.cpp index 44eab7f02d..52e5a7eaa0 100644 --- a/src/core-impl/collections/upnpcollection/UpnpCollectionBase.cpp +++ b/src/core-impl/collections/upnpcollection/UpnpCollectionBase.cpp @@ -1,129 +1,129 @@ /**************************************************************************************** * Copyright (c) 2010 Nikhil Marathe * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "UpnpCollectionBase" #include "UpnpCollectionBase.h" #include "upnptypes.h" #include #include #include #include "core/support/Debug.h" namespace Collections { static const int MAX_JOB_FAILURES_BEFORE_ABORT = 5; UpnpCollectionBase::UpnpCollectionBase( const DeviceInfo& dev ) : Collection() , m_device( dev ) , m_slave( 0 ) , m_slaveConnected( false ) , m_continuousJobFailureCount( 0 ) { KIO::Scheduler::connect( SIGNAL(slaveError(KIO::Slave*,int,QString)), this, SLOT(slotSlaveError(KIO::Slave*,int,QString)) ); KIO::Scheduler::connect( SIGNAL(slaveConnected(KIO::Slave*)), this, SLOT(slotSlaveConnected(KIO::Slave*)) ); m_slave = KIO::Scheduler::getConnectedSlave( collectionId() ); } UpnpCollectionBase::~UpnpCollectionBase() { foreach( KIO::SimpleJob *job, m_jobSet ) KIO::Scheduler::cancelJob( job ); m_jobSet.clear(); if( m_slave ) { KIO::Scheduler::disconnectSlave( m_slave ); m_slave = 0; m_slaveConnected = false; } } QString UpnpCollectionBase::collectionId() const { return QString("upnp-ms://") + m_device.uuid(); } QString UpnpCollectionBase::prettyName() const { return m_device.friendlyName(); } -bool UpnpCollectionBase::possiblyContainsTrack( const KUrl &url ) const +bool UpnpCollectionBase::possiblyContainsTrack( const QUrl &url ) const { if( url.scheme() == "upnp-ms" ) // && url.host() == m_device.host() // && url.port() == m_device.port() ) return true; return false; } void UpnpCollectionBase::addJob( KIO::SimpleJob *job ) { connect( job, SIGNAL(result(KJob*)), this, SLOT(slotRemoveJob(KJob*)) ); m_jobSet.insert( job ); KIO::Scheduler::assignJobToSlave( m_slave, job ); } void UpnpCollectionBase::slotRemoveJob(KJob* job) { KIO::SimpleJob *sj = static_cast( job ); m_jobSet.remove( sj ); if( sj->error() ) { m_continuousJobFailureCount++; if( m_continuousJobFailureCount >= MAX_JOB_FAILURES_BEFORE_ABORT ) { debug() << prettyName() << "Had" << m_continuousJobFailureCount << "continuous job failures, something wrong with the device. Removing this collection."; emit remove(); } } else { m_continuousJobFailureCount = 0; } } void UpnpCollectionBase::slotSlaveError(KIO::Slave* slave, int err, const QString& msg) { debug() << "SLAVE ERROR" << slave << err << msg; if( m_slave != slave ) return; if( err == KIO::ERR_COULD_NOT_CONNECT || err == KIO::ERR_CONNECTION_BROKEN ) { debug() << "COULD NOT CONNECT TO " << msg << "REMOVING THE COLLECTION"; emit remove(); } if( err == KIO::ERR_SLAVE_DIED ) { m_slave = 0; emit remove(); } } void UpnpCollectionBase::slotSlaveConnected(KIO::Slave* slave) { if( m_slave != slave ) return; debug() << "SLAVE IS CONNECTED"; m_slaveConnected = true; } } //namespace Collections diff --git a/src/core-impl/collections/upnpcollection/UpnpCollectionBase.h b/src/core-impl/collections/upnpcollection/UpnpCollectionBase.h index 74b64e204a..18fa9f0b48 100644 --- a/src/core-impl/collections/upnpcollection/UpnpCollectionBase.h +++ b/src/core-impl/collections/upnpcollection/UpnpCollectionBase.h @@ -1,85 +1,85 @@ /**************************************************************************************** * Copyright (c) 2010 Nikhil Marathe * * * * 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, see . * ****************************************************************************************/ #ifndef UPNPCOLLECTIONBASE_H #define UPNPCOLLECTIONBASE_H #include "core/collections/Collection.h" #include #include #include #include #include #include #include #include #include "deviceinfo.h" namespace KIO { class Slave; class Job; class SimpleJob; } class KJob; class QTimer; namespace Collections { class UpnpMemoryQueryMaker; /** * UPnP Collections are of two types. * If a MediaServer only supports the Browse() action * a directory walking, recursive listing UpnpBrowseCollection * is used. * If a server also supports Search(), a more efficient, * UpnpSearchCollection is used. * Certain things are common to both, removal, * track creation from the UDSEntry, collection identification, */ class UpnpCollectionBase : public Collections::Collection { Q_OBJECT public: UpnpCollectionBase( const DeviceInfo& dev ); virtual ~UpnpCollectionBase(); void removeCollection() { emit remove(); } virtual QString collectionId() const; virtual QString prettyName() const; - bool possiblyContainsTrack( const KUrl &url ) const; + bool possiblyContainsTrack( const QUrl &url ) const; private slots: void slotSlaveError( KIO::Slave *slave, int err, const QString &msg ); void slotSlaveConnected( KIO::Slave *slave ); void slotRemoveJob( KJob *job ); protected: void addJob( KIO::SimpleJob *job ); //const Solid::Device m_device; const DeviceInfo m_device; KIO::Slave *m_slave; bool m_slaveConnected; QSet m_jobSet; int m_continuousJobFailureCount; }; } //namespace Collections #endif diff --git a/src/core-impl/collections/upnpcollection/UpnpCollectionFactory.cpp b/src/core-impl/collections/upnpcollection/UpnpCollectionFactory.cpp index ead3909007..7787e15150 100644 --- a/src/core-impl/collections/upnpcollection/UpnpCollectionFactory.cpp +++ b/src/core-impl/collections/upnpcollection/UpnpCollectionFactory.cpp @@ -1,255 +1,255 @@ /**************************************************************************************** * Copyright (c) 2010 Nikhil Marathe * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "UpnpCollectionFactory" #include "UpnpCollectionFactory.h" #include #include #include #include -#include +#include #include #include #include #include "core/support/Debug.h" #include "UpnpBrowseCollection.h" #include "UpnpSearchCollection.h" #include "dbuscodec.h" namespace Collections { AMAROK_EXPORT_COLLECTION( UpnpCollectionFactory, upnpcollection ) UpnpCollectionFactory::UpnpCollectionFactory( QObject *parent, const QVariantList &args ) : Collections::CollectionFactory( parent, args ) { m_info = KPluginInfo( "amarok_collection-upnpcollection.desktop", "services" ); qRegisterMetaType(); qDBusRegisterMetaType< QHash >(); qDBusRegisterMetaType(); qDBusRegisterMetaType(); } UpnpCollectionFactory::~UpnpCollectionFactory() { } void UpnpCollectionFactory::init() { DEBUG_BLOCK if( !cagibi0_1_0Init( QDBusConnection::sessionBus() ) && !cagibi0_1_0Init( QDBusConnection::systemBus() ) && !cagibi0_2_0Init( QDBusConnection::sessionBus() ) && !cagibi0_2_0Init( QDBusConnection::systemBus() ) ) { // we had problems with Cagibi return; } } bool UpnpCollectionFactory::cagibi0_1_0Init( QDBusConnection bus ) { bus.connect( "org.kde.Cagibi", "/org/kde/Cagibi", "org.kde.Cagibi", "devicesAdded", this, SLOT(slotDeviceAdded(DeviceTypeMap)) ); bus.connect( "org.kde.Cagibi", "/org/kde/Cagibi", "org.kde.Cagibi", "devicesRemoved", this, SLOT(slotDeviceRemoved(DeviceTypeMap)) ); m_iface = new QDBusInterface( "org.kde.Cagibi", "/org/kde/Cagibi", "org.kde.Cagibi", bus, this ); QDBusReply reply = m_iface->call( "allDevices" ); if( !reply.isValid() ) { debug() << "ERROR" << reply.error().message(); return false; } else { slotDeviceAdded( reply.value() ); } //Solid::DeviceNotifier *notifier = Solid::DeviceNotifier::instance(); //connect( notifier, SIGNAL(deviceAdded(QString)), this, SLOT(slotDeviceAdded(QString)) ); //connect( notifier, SIGNAL(deviceRemoved(QString)), this, SLOT(slotDeviceRemoved(QString)) ); //foreach( Solid::Device device, Solid::Device::allDevices() ) { // slotDeviceAdded(device.udi()); //} m_initialized = true; return true; } bool UpnpCollectionFactory::cagibi0_2_0Init( QDBusConnection bus ) { bus.connect( "org.kde.Cagibi", "/org/kde/Cagibi/DeviceList", "org.kde.Cagibi.DeviceList", "devicesAdded", this, SLOT(slotDeviceAdded(DeviceTypeMap)) ); bus.connect( "org.kde.Cagibi", "/org/kde/Cagibi/DeviceList", "org.kde.Cagibi.DeviceList", "devicesRemoved", this, SLOT(slotDeviceRemoved(DeviceTypeMap)) ); m_iface = new QDBusInterface( "org.kde.Cagibi", "/org/kde/Cagibi/DeviceList", "org.kde.Cagibi.DeviceList", bus, this ); QDBusReply reply = m_iface->call( "allDevices" ); if( !reply.isValid() ) { debug() << "ERROR" << reply.error().message(); debug() << "Maybe cagibi is not installed."; return false; } else { slotDeviceAdded( reply.value() ); } //Solid::DeviceNotifier *notifier = Solid::DeviceNotifier::instance(); //connect( notifier, SIGNAL(deviceAdded(QString)), this, SLOT(slotDeviceAdded(QString)) ); //connect( notifier, SIGNAL(deviceRemoved(QString)), this, SLOT(slotDeviceRemoved(QString)) ); //foreach( Solid::Device device, Solid::Device::allDevices() ) { // slotDeviceAdded(device.udi()); //} m_initialized = true; return true; } void UpnpCollectionFactory::slotDeviceAdded( const DeviceTypeMap &map ) { foreach( const QString &udn, map.keys() ) { QString type = map[udn]; debug() << "|||| DEVICE" << udn << type; if( type.startsWith("urn:schemas-upnp-org:device:MediaServer") ) createCollection( udn ); } } void UpnpCollectionFactory::slotDeviceRemoved( const DeviceTypeMap &map ) { foreach( QString udn, map.keys() ) { udn.remove("uuid:"); if( m_devices.contains(udn) ) { m_devices[udn]->removeCollection(); m_devices.remove(udn); } } } void UpnpCollectionFactory::createCollection( const QString &udn ) { debug() << "|||| Creating collection " << udn; DeviceInfo info; if( !cagibi0_1_0DeviceDetails( udn, &info ) && !cagibi0_2_0DeviceDetails( udn, &info ) ) { return; } debug() << "|||| Creating collection " << info.uuid(); KIO::ListJob *job = KIO::listDir( QString( "upnp-ms://" + info.uuid() + "/?searchcapabilities=1" ) ); job->setProperty( "deviceInfo", QVariant::fromValue( info ) ); connect( job, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), this, SLOT(slotSearchEntries(KIO::Job*,KIO::UDSEntryList)) ); connect( job, SIGNAL(result(KJob*)), this, SLOT(slotSearchCapabilitiesDone(KJob*)) ); } bool UpnpCollectionFactory::cagibi0_1_0DeviceDetails( const QString &udn, DeviceInfo *info ) { QDBusReply reply = m_iface->call( "deviceDetails", udn ); if( !reply.isValid() ) { debug() << "Invalid reply from deviceDetails for" << udn << ". Skipping"; debug() << "Error" << reply.error().message(); return false; } *info = reply.value(); return true; } bool UpnpCollectionFactory::cagibi0_2_0DeviceDetails( const QString &udn, DeviceInfo *info ) { QDBusReply reply = m_iface->call( "deviceDetails", udn ); if( !reply.isValid() ) { debug() << "Invalid reply from deviceDetails for" << udn << ". Skipping"; debug() << "Error" << reply.error().message(); return false; } foreach( const QString &k, reply.value().keys() ) debug() << k << reply.value()[k]; DeviceInfo0_2_0 v( reply.value() ); *info = v; return true; } void UpnpCollectionFactory::slotSearchEntries( KIO::Job *job, const KIO::UDSEntryList &list ) { Q_UNUSED( job ); KIO::ListJob *lj = static_cast( job ); foreach( const KIO::UDSEntry &entry, list ) m_capabilities[lj->url().host()] << entry.stringValue( KIO::UDSEntry::UDS_NAME ); } void UpnpCollectionFactory::slotSearchCapabilitiesDone( KJob *job ) { KIO::ListJob *lj = static_cast( job ); QStringList searchCaps = m_capabilities[lj->url().host()]; if( !job->error() ) { DeviceInfo dev = job->property( "deviceInfo" ).value(); if( searchCaps.contains( "upnp:class" ) && searchCaps.contains( "dc:title" ) && searchCaps.contains( "upnp:artist" ) && searchCaps.contains( "upnp:album" ) ) { kDebug() << "Supports all search meta-data required, using UpnpSearchCollection"; m_devices[dev.uuid()] = new UpnpSearchCollection( dev, searchCaps ); } else { kDebug() << "Supported Search() meta-data" << searchCaps << "not enough. Using UpnpBrowseCollection"; m_devices[dev.uuid()] = new UpnpBrowseCollection( dev ); } emit newCollection( m_devices[dev.uuid()] ); } } } diff --git a/src/core-impl/collections/upnpcollection/UpnpMeta.cpp b/src/core-impl/collections/upnpcollection/UpnpMeta.cpp index 9b660de5e9..e555b8f082 100644 --- a/src/core-impl/collections/upnpcollection/UpnpMeta.cpp +++ b/src/core-impl/collections/upnpcollection/UpnpMeta.cpp @@ -1,561 +1,561 @@ /**************************************************************************************** * Copyright (c) 2010 Nikhil Marathe * * * * 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, see . * ****************************************************************************************/ #include "UpnpMeta.h" #include "UpnpCollectionBase.h" #include "core/support/Debug.h" #include "covermanager/CoverCache.h" #include "covermanager/CoverFetchingActions.h" #include "core-impl/capabilities/AlbumActionsCapability.h" #include #include using namespace Meta; UpnpTrack::UpnpTrack( Collections::UpnpCollectionBase *collection ) : Meta::Track() , m_collection( collection ) , m_album( 0 ) , m_genre( 0 ) , m_composer( 0 ) , m_year( 0 ) { } UpnpTrack::~UpnpTrack() { //nothing to do } QString UpnpTrack::name() const { return m_name; } -KUrl +QUrl UpnpTrack::playableUrl() const { - KUrl url( m_playableUrl ); + QUrl url( m_playableUrl ); return url; } QString UpnpTrack::uidUrl() const { return m_uidUrl; } QString UpnpTrack::prettyUrl() const { return m_playableUrl; } QString UpnpTrack::notPlayableReason() const { return networkNotPlayableReason(); } AlbumPtr UpnpTrack::album() const { return AlbumPtr::staticCast( m_album ); } ArtistPtr UpnpTrack::artist() const { return ArtistPtr::staticCast( m_artist ); } GenrePtr UpnpTrack::genre() const { return GenrePtr::staticCast( m_genre ); } ComposerPtr UpnpTrack::composer() const { return ComposerPtr::staticCast( m_composer ); } YearPtr UpnpTrack::year() const { return YearPtr::staticCast( m_year ); } void UpnpTrack::setAlbum( const QString &newAlbum ) { Q_UNUSED( newAlbum ) } void UpnpTrack::setArtist( const QString &newArtist ) { Q_UNUSED( newArtist ) } void UpnpTrack::setComposer( const QString &newComposer ) { Q_UNUSED( newComposer ) } void UpnpTrack::setGenre( const QString &newGenre ) { Q_UNUSED( newGenre ) } void UpnpTrack::setYear( int newYear ) { Q_UNUSED( newYear ) } void UpnpTrack::setUidUrl( const QString &url ) { // TODO should we include uuid() also in the url? m_uidUrl = url; if( !url.startsWith( "upnp-ms://" ) ) m_uidUrl = "upnp-ms://" + m_collection->collectionId() + "/" + m_uidUrl; } void UpnpTrack::setPlayableUrl( const QString& url ) { m_playableUrl = url; } /* TODO: This isn't good enough, but for now as daapreader/Reader.cpp indicates we can query for the BPM from daap server, but desire is to get BPM of files working first! */ qreal UpnpTrack::bpm() const { return -1.0; } QString UpnpTrack::comment() const { return QString(); } void UpnpTrack::setComment( const QString &newComment ) { Q_UNUSED( newComment ) } qint64 UpnpTrack::length() const { return m_length; } int UpnpTrack::filesize() const { return 0; } int UpnpTrack::sampleRate() const { return 0; } int UpnpTrack::bitrate() const { return m_bitrate; } int UpnpTrack::trackNumber() const { return m_trackNumber; } void UpnpTrack::setTrackNumber( int newTrackNumber ) { m_trackNumber = newTrackNumber; } int UpnpTrack::discNumber() const { return 0; } void UpnpTrack::setDiscNumber( int newDiscNumber ) { Q_UNUSED( newDiscNumber ) } QString UpnpTrack::type() const { return m_type; } bool UpnpTrack::inCollection() const { return true; } Collections::Collection* UpnpTrack::collection() const { return m_collection; } void UpnpTrack::setAlbum( UpnpAlbumPtr album ) { m_album = album; } void UpnpTrack::setArtist( UpnpArtistPtr artist ) { m_artist = artist; } void UpnpTrack::setGenre( UpnpGenrePtr genre ) { m_genre = genre; } void UpnpTrack::setComposer( UpnpComposerPtr composer ) { m_composer = composer; } void UpnpTrack::setYear( UpnpYearPtr year ) { m_year = year; } void UpnpTrack::setTitle( const QString &title ) { m_name = title; } void UpnpTrack::setLength( qint64 length ) { m_length = length; } void UpnpTrack::setBitrate( int rate ) { m_bitrate = rate; } //UpnpArtist UpnpArtist::UpnpArtist( const QString &name ) : Meta::Artist() , m_name( name ) , m_tracks() { //nothing to do } UpnpArtist::~UpnpArtist() { //nothing to do } QString UpnpArtist::name() const { return m_name; } TrackList UpnpArtist::tracks() { return m_tracks; } void UpnpArtist::addTrack( UpnpTrackPtr track ) { m_tracks.append( TrackPtr::staticCast( track ) ); } void UpnpArtist::removeTrack( UpnpTrackPtr track ) { m_tracks.removeOne( TrackPtr::staticCast( track ) ); } UpnpAlbum::UpnpAlbum( const QString &name ) : QObject() , Meta::Album() , m_name( name ) , m_tracks() , m_isCompilation( false ) , m_albumArtist( 0 ) { //nothing to do } UpnpAlbum::~UpnpAlbum() { CoverCache::invalidateAlbum( this ); } QString UpnpAlbum::name() const { return m_name; } bool UpnpAlbum::isCompilation() const { return m_isCompilation; } bool UpnpAlbum::hasAlbumArtist() const { return !m_albumArtist.isNull(); } ArtistPtr UpnpAlbum::albumArtist() const { return ArtistPtr::staticCast( m_albumArtist ); } TrackList UpnpAlbum::tracks() { return m_tracks; } bool UpnpAlbum::hasImage( int size ) const { Q_UNUSED( size ); return m_albumArtUrl.isValid(); } QImage UpnpAlbum::image( int size ) const { if( m_image.isNull() ) { QString path; if( m_albumArtUrl.isValid() && KIO::NetAccess::download( m_albumArtUrl, path, NULL ) ) { m_image = QImage( path ); CoverCache::invalidateAlbum( this ); } } if( m_image.isNull() ) return Meta::Album::image( size ); return size <= 1 ? m_image : m_image.scaled( size, size ); } -KUrl +QUrl UpnpAlbum::imageLocation( int size ) { Q_UNUSED( size ); return m_albumArtUrl; } void UpnpAlbum::addTrack( UpnpTrackPtr track ) { m_tracks.append( TrackPtr::staticCast( track ) ); } void UpnpAlbum::removeTrack( UpnpTrackPtr track ) { m_tracks.removeOne( TrackPtr::staticCast( track ) ); } void UpnpAlbum::setAlbumArtist( UpnpArtistPtr artist ) { m_albumArtist = artist; } void -UpnpAlbum::setAlbumArtUrl( const KUrl &url ) +UpnpAlbum::setAlbumArtUrl( const QUrl &url ) { m_albumArtUrl = url; } Capabilities::Capability* UpnpAlbum::createCapabilityInterface( Capabilities::Capability::Type type ) { switch( type ) { case Capabilities::Capability::Actions: return new Capabilities::AlbumActionsCapability( Meta::AlbumPtr( this ) ); default: return 0; } } //UpnpGenre UpnpGenre::UpnpGenre( const QString &name ) : Meta::Genre() , m_name( name ) , m_tracks() { //nothing to do } UpnpGenre::~UpnpGenre() { //nothing to do } QString UpnpGenre::name() const { return m_name; } TrackList UpnpGenre::tracks() { return m_tracks; } void UpnpGenre::addTrack( UpnpTrackPtr track ) { m_tracks.append( TrackPtr::staticCast( track ) ); } void UpnpGenre::removeTrack( UpnpTrackPtr track ) { m_tracks.removeOne( TrackPtr::staticCast( track ) ); } //UpnpComposer UpnpComposer::UpnpComposer( const QString &name ) : Meta::Composer() , m_name( name ) , m_tracks() { //nothing to do } UpnpComposer::~UpnpComposer() { //nothing to do } QString UpnpComposer::name() const { return m_name; } TrackList UpnpComposer::tracks() { return m_tracks; } void UpnpComposer::addTrack( UpnpTrackPtr track ) { m_tracks.append( TrackPtr::staticCast( track ) ); } void UpnpComposer::removeTrack( UpnpTrackPtr track ) { m_tracks.removeOne( TrackPtr::staticCast( track ) ); } //UpnpYear UpnpYear::UpnpYear( int name ) : Meta::Year() , m_name( name ) , m_tracks() { //nothing to do } UpnpYear::~UpnpYear() { //nothing to do } QString UpnpYear::name() const { return m_name; } TrackList UpnpYear::tracks() { return m_tracks; } void UpnpYear::addTrack( UpnpTrackPtr track ) { m_tracks.append( TrackPtr::staticCast( track ) ); } void UpnpYear::removeTrack( UpnpTrackPtr track ) { m_tracks.removeOne( TrackPtr::staticCast( track ) ); } diff --git a/src/core-impl/collections/upnpcollection/UpnpMeta.h b/src/core-impl/collections/upnpcollection/UpnpMeta.h index c5f859fe8f..e4c3a69b2a 100644 --- a/src/core-impl/collections/upnpcollection/UpnpMeta.h +++ b/src/core-impl/collections/upnpcollection/UpnpMeta.h @@ -1,238 +1,238 @@ /**************************************************************************************** * Copyright (c) 2010 Nikhil Marathe * * * * 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, see . * ****************************************************************************************/ #ifndef UPNPMETA_H #define UPNPMETA_H #include "core/meta/Meta.h" namespace Collections { class UpnpCollectionBase; } namespace Meta { class UpnpTrack; class UpnpAlbum; class UpnpArtist; class UpnpGenre; class UpnpComposer; class UpnpYear; typedef KSharedPtr UpnpTrackPtr; typedef KSharedPtr UpnpArtistPtr; typedef KSharedPtr UpnpAlbumPtr; typedef KSharedPtr UpnpGenrePtr; typedef KSharedPtr UpnpComposerPtr; typedef KSharedPtr UpnpYearPtr; class UpnpTrack : public Meta::Track { public: UpnpTrack( Collections::UpnpCollectionBase *collection ); virtual ~UpnpTrack(); virtual QString name() const; - virtual KUrl playableUrl() const; + virtual QUrl playableUrl() const; virtual QString uidUrl() const; virtual QString prettyUrl() const; virtual QString notPlayableReason() const; virtual AlbumPtr album() const; virtual ArtistPtr artist() const; virtual GenrePtr genre() const; virtual ComposerPtr composer() const; virtual YearPtr year() const; virtual void setAlbum ( const QString &newAlbum ); virtual void setArtist ( const QString &newArtist ); virtual void setGenre ( const QString &newGenre ); virtual void setComposer ( const QString &newComposer ); virtual void setYear ( int year ); virtual void setTitle( const QString &newTitle ); virtual void setUidUrl( const QString &url ); virtual qreal bpm() const; virtual QString comment() const; virtual void setComment ( const QString &newComment ); virtual qint64 length() const; virtual int filesize() const; virtual int sampleRate() const; virtual int bitrate() const; virtual int trackNumber() const; virtual void setTrackNumber ( int newTrackNumber ); virtual int discNumber() const; virtual void setDiscNumber ( int newDiscNumber ); virtual QString type() const; virtual bool inCollection() const; virtual Collections::Collection* collection() const; //UpnpTrack specific methods void setAlbum( UpnpAlbumPtr album ); void setArtist( UpnpArtistPtr artist ); void setComposer( UpnpComposerPtr composer ); void setGenre( UpnpGenrePtr genre ); void setYear( UpnpYearPtr year ); void setPlayableUrl( const QString &url ); void setLength( qint64 length ); void setBitrate( int rate ); private: Collections::UpnpCollectionBase *m_collection; UpnpArtistPtr m_artist; UpnpAlbumPtr m_album; UpnpGenrePtr m_genre; UpnpComposerPtr m_composer; UpnpYearPtr m_year; QString m_name; QString m_type; qint64 m_length; int m_bitrate; int m_trackNumber; QString m_displayUrl; QString m_playableUrl; QString m_uidUrl; }; class UpnpArtist : public Meta::Artist { public: UpnpArtist( const QString &name ); virtual ~UpnpArtist(); virtual QString name() const; virtual TrackList tracks(); //UpnpArtist specific methods void addTrack( UpnpTrackPtr track ); void removeTrack( UpnpTrackPtr track ); private: QString m_name; TrackList m_tracks; }; class UpnpAlbum : public QObject, public Meta::Album { Q_OBJECT public: UpnpAlbum( const QString &name ); virtual ~UpnpAlbum(); virtual QString name() const; virtual bool isCompilation() const; virtual bool hasAlbumArtist() const; virtual ArtistPtr albumArtist() const; virtual TrackList tracks(); virtual bool hasImage( int size = 0 ) const; virtual QImage image( int size = 0 ) const; - virtual KUrl imageLocation( int size = 0 ); + virtual QUrl imageLocation( int size = 0 ); virtual Capabilities::Capability* createCapabilityInterface( Capabilities::Capability::Type type ); //UpnpAlbum specific methods void addTrack( UpnpTrackPtr track ); void removeTrack( UpnpTrackPtr track ); void setAlbumArtist( UpnpArtistPtr artist ); - void setAlbumArtUrl( const KUrl &url ); + void setAlbumArtUrl( const QUrl &url ); private: QString m_name; mutable QImage m_image; TrackList m_tracks; bool m_isCompilation; UpnpArtistPtr m_albumArtist; - KUrl m_albumArtUrl; + QUrl m_albumArtUrl; }; class UpnpGenre : public Meta::Genre { public: UpnpGenre( const QString &name ); virtual ~UpnpGenre(); virtual QString name() const; virtual TrackList tracks(); //UpnpGenre specific methods void addTrack( UpnpTrackPtr track ); void removeTrack( UpnpTrackPtr track ); private: QString m_name; TrackList m_tracks; }; class UpnpComposer : public Meta::Composer { public: UpnpComposer( const QString &name ); virtual ~UpnpComposer(); virtual QString name() const; virtual TrackList tracks(); //UpnpComposer specific methods void addTrack( UpnpTrackPtr track ); void removeTrack( UpnpTrackPtr track ); private: QString m_name; TrackList m_tracks; }; class UpnpYear : public Meta::Year { public: UpnpYear( int year ); virtual ~UpnpYear(); virtual QString name() const; virtual TrackList tracks(); //UpnpYear specific methods void addTrack( UpnpTrackPtr track ); void removeTrack( UpnpTrackPtr track ); private: QString m_name; TrackList m_tracks; }; } #endif diff --git a/src/core-impl/collections/upnpcollection/UpnpQueryMaker.cpp b/src/core-impl/collections/upnpcollection/UpnpQueryMaker.cpp index 4b311909ca..c563e7f268 100644 --- a/src/core-impl/collections/upnpcollection/UpnpQueryMaker.cpp +++ b/src/core-impl/collections/upnpcollection/UpnpQueryMaker.cpp @@ -1,543 +1,543 @@ /**************************************************************************************** * Copyright (c) 2010 Nikhil Marathe * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "UpnpQueryMaker" #include "UpnpQueryMaker.h" #include #include "upnptypes.h" #include #include #include "core/support/Debug.h" #include "UpnpSearchCollection.h" #include "UpnpQueryMakerInternal.h" #include "UpnpMeta.h" #include "UpnpCache.h" namespace Collections { UpnpQueryMaker::UpnpQueryMaker( UpnpSearchCollection *collection ) : QueryMaker() , m_collection( collection ) , m_internalQM( new UpnpQueryMakerInternal( collection ) ) { reset(); connect( m_internalQM, SIGNAL(done()), this, SLOT(slotDone()) ); connect( m_internalQM, SIGNAL(newResultReady(Meta::TrackList)), this, SLOT(handleTracks(Meta::TrackList)) ); connect( m_internalQM, SIGNAL(newResultReady(Meta::ArtistList)), this, SLOT(handleArtists(Meta::ArtistList)) ); connect( m_internalQM, SIGNAL(newResultReady(Meta::AlbumList)), this, SLOT(handleAlbums(Meta::AlbumList)) ); connect( m_internalQM, SIGNAL(newResultReady(KIO::UDSEntryList)), this, SLOT(handleCustom(KIO::UDSEntryList)) ); } UpnpQueryMaker::~UpnpQueryMaker() { m_internalQM->deleteLater(); } QueryMaker* UpnpQueryMaker::reset() { // TODO kill all jobs here too m_queryType = None; m_albumMode = AllAlbums; m_query.reset(); m_jobCount = 0; m_numericFilters.clear(); m_internalQM->reset(); // the Amarok Collection Model expects at least one entry // otherwise it will harass us continuously for more entries. // of course due to the poor quality of UPnP servers I've // had experience with :P, some may not have sub-results // for something ( they may have a track with an artist, but // not be able to give any album for it ) m_noResults = true; return this; } void UpnpQueryMaker::run() { DEBUG_BLOCK - KUrl baseUrl( m_collection->collectionId() ); + QUrl baseUrl( m_collection->collectionId() ); baseUrl.addQueryItem( "search", "1" ); if( m_queryType == Custom ) { switch( m_returnFunction ) { case Count: m_query.reset(); m_query.setType( "( upnp:class derivedfrom \"object.item.audioItem\" )" ); baseUrl.addQueryItem( "getCount", "1" ); break; case Sum: case Max: case Min: break; } } // we don't deal with compilations else if( m_queryType == Album && m_albumMode == OnlyCompilations ) { // we don't support any other attribute emit newResultReady( Meta::TrackList() ); emit newResultReady( Meta::ArtistList() ); emit newResultReady( Meta::AlbumList() ); emit newResultReady( Meta::GenreList() ); emit newResultReady( Meta::ComposerList() ); emit newResultReady( Meta::YearList() ); emit newResultReady( QStringList() ); emit newResultReady( Meta::LabelList() ); emit queryDone(); return; } QStringList queryList; if( m_query.hasMatchFilter() || !m_numericFilters.empty() ) { queryList = m_query.queries(); } else { switch( m_queryType ) { case Artist: debug() << this << "Query type Artist"; queryList << "( upnp:class derivedfrom \"object.container.person.musicArtist\" )"; break; case Album: debug() << this << "Query type Album"; queryList << "( upnp:class derivedfrom \"object.container.album.musicAlbum\" )"; break; case Track: debug() << this << "Query type Track"; queryList << "( upnp:class derivedfrom \"object.item.audioItem\" )"; break; case Genre: debug() << this << "Query type Genre"; queryList << "( upnp:class derivedfrom \"object.container.genre.musicGenre\" )"; break; case Custom: debug() << this << "Query type Custom"; queryList << "( upnp:class derivedfrom \"object.item.audioItem\" )"; break; default: debug() << this << "Default case: Query type"; // we don't support any other attribute emit newResultReady( Meta::TrackList() ); emit newResultReady( Meta::ArtistList() ); emit newResultReady( Meta::AlbumList() ); emit newResultReady( Meta::GenreList() ); emit newResultReady( Meta::ComposerList() ); emit newResultReady( Meta::YearList() ); emit newResultReady( QStringList() ); emit newResultReady( Meta::LabelList() ); emit queryDone(); return; } } // and experiment in using the filter only for the query // and checking the returned upnp:class // based on your query types. for( int i = 0; i < queryList.length() ; i++ ) { if( queryList[i].isEmpty() ) continue; - KUrl url( baseUrl ); + QUrl url( baseUrl ); url.addQueryItem( "query", queryList[i] ); debug() << this << "Running query" << url; m_internalQM->runQuery( url ); } } void UpnpQueryMaker::abortQuery() { DEBUG_BLOCK Q_ASSERT( false ); // TODO implement this to kill job } QueryMaker* UpnpQueryMaker::setQueryType( QueryType type ) { DEBUG_BLOCK // TODO allow all, based on search capabilities // which should be passed on by the factory m_queryType = type; m_query.setType( "( upnp:class derivedfrom \"object.item.audioItem\" )" ); m_internalQM->setQueryType( type ); return this; } QueryMaker* UpnpQueryMaker::addReturnValue( qint64 value ) { DEBUG_BLOCK debug() << this << "Add return value" << value; m_returnValue = value; return this; } QueryMaker* UpnpQueryMaker::addReturnFunction( ReturnFunction function, qint64 value ) { DEBUG_BLOCK Q_UNUSED( function ) debug() << this << "Return function with value" << value; m_returnFunction = function; m_returnValue = value; return this; } QueryMaker* UpnpQueryMaker::orderBy( qint64 value, bool descending ) { DEBUG_BLOCK debug() << this << "Order by " << value << "Descending?" << descending; return this; } QueryMaker* UpnpQueryMaker::addMatch( const Meta::TrackPtr &track ) { DEBUG_BLOCK debug() << this << "Adding track match" << track->name(); // TODO: CHECK query type before searching by dc:title? m_query.addMatch( "( dc:title = \"" + track->name() + "\" )" ); return this; } QueryMaker* UpnpQueryMaker::addMatch( const Meta::ArtistPtr &artist, QueryMaker::ArtistMatchBehaviour behaviour ) { DEBUG_BLOCK Q_UNUSED( behaviour ); // TODO: does UPnP tell between track and album artists? debug() << this << "Adding artist match" << artist->name(); m_query.addMatch( "( upnp:artist = \"" + artist->name() + "\" )" ); return this; } QueryMaker* UpnpQueryMaker::addMatch( const Meta::AlbumPtr &album ) { DEBUG_BLOCK debug() << this << "Adding album match" << album->name(); m_query.addMatch( "( upnp:album = \"" + album->name() + "\" )" ); return this; } QueryMaker* UpnpQueryMaker::addMatch( const Meta::ComposerPtr &composer ) { DEBUG_BLOCK debug() << this << "Adding composer match" << composer->name(); // NOTE unsupported return this; } QueryMaker* UpnpQueryMaker::addMatch( const Meta::GenrePtr &genre ) { DEBUG_BLOCK debug() << this << "Adding genre match" << genre->name(); m_query.addMatch( "( upnp:genre = \"" + genre->name() + "\" )" ); return this; } QueryMaker* UpnpQueryMaker::addMatch( const Meta::YearPtr &year ) { DEBUG_BLOCK debug() << this << "Adding year match" << year->name(); // TODO return this; } QueryMaker* UpnpQueryMaker::addMatch( const Meta::LabelPtr &label ) { DEBUG_BLOCK debug() << this << "Adding label match" << label->name(); // NOTE how? return this; } QueryMaker* UpnpQueryMaker::addFilter( qint64 value, const QString &filter, bool matchBegin, bool matchEnd ) { DEBUG_BLOCK debug() << this << "Adding filter" << value << filter << matchBegin << matchEnd; // theoretically this should be '=' I think and set to contains below if required QString cmpOp = "contains"; //TODO should we add filters ourselves // eg. we always query for audioItems, but how do we decide // whether to add a dc:title filter or others. // for example, for the artist list // our query should be like ( pseudocode ) // ( upnp:class = audioItem ) and ( dc:title contains "filter" ) // OR // ( upnp:class = audioItem ) and ( upnp:artist contains "filter" ); // ... // so who adds the second query? QString property = propertyForValue( value ); if( property.isNull() ) return this; if( matchBegin || matchEnd ) cmpOp = "contains"; QString filterString = "( " + property + " " + cmpOp + " \"" + filter + "\" ) "; m_query.addFilter( filterString ); return this; } QueryMaker* UpnpQueryMaker::excludeFilter( qint64 value, const QString &filter, bool matchBegin, bool matchEnd ) { DEBUG_BLOCK debug() << this << "Excluding filter" << value << filter << matchBegin << matchEnd; QString cmpOp = "!="; QString property = propertyForValue( value ); if( property.isNull() ) return this; if( matchBegin || matchEnd ) cmpOp = "doesNotContain"; QString filterString = "( " + property + " " + cmpOp + " \"" + filter + "\" ) "; m_query.addFilter( filterString ); return this; } QueryMaker* UpnpQueryMaker::addNumberFilter( qint64 value, qint64 filter, NumberComparison compare ) { DEBUG_BLOCK debug() << this << "Adding number filter" << value << filter << compare; NumericFilter f = { value, filter, compare }; m_numericFilters << f; return this; } QueryMaker* UpnpQueryMaker::excludeNumberFilter( qint64 value, qint64 filter, NumberComparison compare ) { DEBUG_BLOCK debug() << this << "Excluding number filter" << value << filter << compare; return this; } QueryMaker* UpnpQueryMaker::limitMaxResultSize( int size ) { DEBUG_BLOCK debug() << this << "Limit max results to" << size; return this; } QueryMaker* UpnpQueryMaker::setAlbumQueryMode( AlbumQueryMode mode ) { DEBUG_BLOCK debug() << this << "Set album query mode" << mode; m_albumMode = mode; return this; } QueryMaker* UpnpQueryMaker::setLabelQueryMode( LabelQueryMode mode ) { DEBUG_BLOCK debug() << this << "Set label query mode" << mode; return this; } QueryMaker* UpnpQueryMaker::beginAnd() { DEBUG_BLOCK m_query.beginAnd(); return this; } QueryMaker* UpnpQueryMaker::beginOr() { DEBUG_BLOCK m_query.beginOr(); return this; } QueryMaker* UpnpQueryMaker::endAndOr() { DEBUG_BLOCK debug() << this << "End AND/OR"; m_query.endAndOr(); return this; } QueryMaker* UpnpQueryMaker::setAutoDelete( bool autoDelete ) { DEBUG_BLOCK debug() << this << "Auto delete" << autoDelete; return this; } int UpnpQueryMaker::validFilterMask() { int mask = 0; QStringList caps = m_collection->searchCapabilities(); if( caps.contains( "dc:title" ) ) mask |= TitleFilter; if( caps.contains( "upnp:album" ) ) mask |= AlbumFilter; if( caps.contains( "upnp:artist" ) ) mask |= ArtistFilter; if( caps.contains( "upnp:genre" ) ) mask |= GenreFilter; return mask; } void UpnpQueryMaker::handleArtists( Meta::ArtistList list ) { // TODO Post filtering emit newResultReady( list ); } void UpnpQueryMaker::handleAlbums( Meta::AlbumList list ) { // TODO Post filtering emit newResultReady( list ); } void UpnpQueryMaker::handleTracks( Meta::TrackList list ) { // TODO Post filtering emit newResultReady( list ); } /* void UpnpQueryMaker::handleCustom( const KIO::UDSEntryList& list ) { if( m_returnFunction == Count ) { { Q_ASSERT( !list.empty() ); QString count = list.first().stringValue( KIO::UDSEntry::UDS_NAME ); m_collection->setProperty( "numberOfTracks", count.toUInt() ); emit newResultReady( QStringList( count ) ); } default: debug() << "Custom result functions other than \"Count\" are not supported by UpnpQueryMaker"; } } */ void UpnpQueryMaker::slotDone() { DEBUG_BLOCK if( m_noResults ) { debug() << "++++++++++++++++++++++++++++++++++++ NO RESULTS ++++++++++++++++++++++++"; // TODO proper data types not just DataPtr Meta::DataList ret; Meta::UpnpTrack *fake = new Meta::UpnpTrack( m_collection ); fake->setTitle( "No results" ); fake->setYear( Meta::UpnpYearPtr( new Meta::UpnpYear( 2010 ) ) ); Meta::DataPtr ptr( fake ); ret << ptr; //emit newResultReady( ret ); } switch( m_queryType ) { case Artist: { Meta::ArtistList list; foreach( Meta::DataPtr ptr, m_cacheEntries ) list << Meta::ArtistPtr::staticCast( ptr ); emit newResultReady( list ); break; } case Album: { Meta::AlbumList list; foreach( Meta::DataPtr ptr, m_cacheEntries ) list << Meta::AlbumPtr::staticCast( ptr ); emit newResultReady( list ); break; } case Track: { Meta::TrackList list; foreach( Meta::DataPtr ptr, m_cacheEntries ) list << Meta::TrackPtr::staticCast( ptr ); emit newResultReady( list ); break; } default: { debug() << "Query type not supported by UpnpQueryMaker"; } } debug() << "ALL JOBS DONE< TERMINATING THIS QM" << this; emit queryDone(); } QString UpnpQueryMaker::propertyForValue( qint64 value ) { switch( value ) { case Meta::valTitle: return "dc:title"; case Meta::valArtist: { //if( m_queryType != Artist ) return "upnp:artist"; } case Meta::valAlbum: { //if( m_queryType != Album ) return "upnp:album"; } case Meta::valGenre: return "upnp:genre"; break; default: debug() << "UNSUPPORTED QUERY TYPE" << value; return QString(); } } bool UpnpQueryMaker::postFilter( const KIO::UDSEntry &entry ) { //numeric filters foreach( const NumericFilter &filter, m_numericFilters ) { // should be set by the filter based on filter.type qint64 aValue = 0; switch( filter.type ) { case Meta::valCreateDate: { // TODO might use UDSEntry::UDS_CREATION_TIME instead later QString dateString = entry.stringValue( KIO::UPNP_DATE ); QDateTime time = QDateTime::fromString( dateString, Qt::ISODate ); if( !time.isValid() ) return false; aValue = time.toTime_t(); debug() << "FILTER BY creation timestamp entry:" << aValue << "query:" << filter.value << "OP:" << filter.compare; break; } } if( ( filter.compare == Equals ) && ( filter.value != aValue ) ) return false; else if( ( filter.compare == GreaterThan ) && ( filter.value >= aValue ) ) return false; // since only allow entries with aValue > filter.value else if( ( filter.compare == LessThan ) && ( filter.value <= aValue ) ) return false; } return true; } } //namespace Collections diff --git a/src/core-impl/collections/upnpcollection/UpnpQueryMakerInternal.cpp b/src/core-impl/collections/upnpcollection/UpnpQueryMakerInternal.cpp index 04488793a6..62cfcc7d71 100644 --- a/src/core-impl/collections/upnpcollection/UpnpQueryMakerInternal.cpp +++ b/src/core-impl/collections/upnpcollection/UpnpQueryMakerInternal.cpp @@ -1,250 +1,250 @@ /**************************************************************************************** * Copyright (c) 2010 Nikhil Marathe * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "UpnpQueryMakerInternal" #include "UpnpQueryMakerInternal.h" #include "upnptypes.h" #include #include #include "UpnpSearchCollection.h" #include "UpnpCache.h" #include "UpnpMeta.h" #include "core/support/Debug.h" namespace Collections { // use filter for faster data transfer and parsing // if cached tracks > remote tracks * CACHE_CHECK_THRESHOLD static const float CACHE_CHECK_THRESHOLD = 0.75f; UpnpQueryMakerInternal::UpnpQueryMakerInternal( UpnpSearchCollection *collection ) : m_collection( collection ) { reset(); } void UpnpQueryMakerInternal::reset() { m_queryType = QueryMaker::None; m_jobCount = 0; } UpnpQueryMakerInternal::~UpnpQueryMakerInternal() { } void UpnpQueryMakerInternal::queueJob(KIO::SimpleJob* job) { - KUrl url = job->url(); - debug() << "+-+- RUNNING JOB WITH" << url.prettyUrl(); + QUrl url = job->url(); + debug() << "+-+- RUNNING JOB WITH" << url.toDisplayString(); m_collection->addJob( job ); m_jobCount++; job->start(); } -void UpnpQueryMakerInternal::runQuery( KUrl query, bool filter ) +void UpnpQueryMakerInternal::runQuery( QUrl query, bool filter ) { // insert this query as a job // first check cache size vs remote size // if over threshold, apply filter, otherwise pass on as normal int remoteCount = m_collection->property( "numberOfTracks" ).toInt(); debug() << "REMOTE COUNT" << remoteCount << "Cache size" << m_collection->cache()->tracks().size(); if( m_collection->cache()->tracks().size() > remoteCount * CACHE_CHECK_THRESHOLD && remoteCount > 0 && filter ) { debug() << "FILTERING BY CLASS ONLY"; query.addQueryItem( "filter", "upnp:class" ); } KIO::ListJob *job = KIO::listDir( query, KIO::HideProgressInfo ); connect( job, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), this, SLOT(slotEntries(KIO::Job*,KIO::UDSEntryList)) ); connect( job, SIGNAL(result(KJob*)), this, SLOT(slotDone(KJob*)) ); queueJob( job ); } void UpnpQueryMakerInternal::runStat( const QString& id ) { - KUrl url( m_collection->collectionId() ); + QUrl url( m_collection->collectionId() ); url.addQueryItem( "id", id ); debug() << "STAT URL" << url; KIO::StatJob *job = KIO::stat( url, KIO::HideProgressInfo ); connect( job, SIGNAL(result(KJob*)), this, SLOT(slotStatDone(KJob*)) ); queueJob( job ); } void UpnpQueryMakerInternal::slotEntries( KIO::Job *job, const KIO::UDSEntryList &list ) { debug() << "+-+- JOB DONE" << static_cast(job)->url() << job->error(); foreach( const KIO::UDSEntry &entry, list ) debug() << "GOT ENTRY " << entry.stringValue( KIO::UDSEntry::UDS_NAME ); // actually iterate over the list, check for cache hits // if hit, get the relevant Meta::*Ptr object and pass it on // for post filtering etc. // if miss, queue up another job to fetch all details // job->url() can be used to decide if complete details // are available or not using queryItem("filter") details // if( job->error() ) emit results( true, KIO::UDSEntryList() ); else emit results( false, list ); debug() << this << "SLOT ENTRIES" << list.length() << m_queryType; switch( m_queryType ) { case QueryMaker::Artist: handleArtists( list ); break; case QueryMaker::Album: handleAlbums( list ); break; case QueryMaker::Track: handleTracks( list ); break; case QueryMaker::Custom: handleCustom( list ); break; default: break; // TODO handle remaining cases } if( !list.empty() ) { debug() << "_______________________ RESULTS! ____________________________"; } } void UpnpQueryMakerInternal::slotDone( KJob *job ) { // here check if all jobs done, then we might want to emit done() // clean up this job, remove it from the hash and so on. m_jobCount--; job->deleteLater(); if( m_jobCount <= 0 ) { //emit newResultReady( list ); debug() << "ALL JOBS DONE< TERMINATING THIS QM" << this; emit done(); } } void UpnpQueryMakerInternal::slotStatDone( KJob *job ) { m_jobCount--; KIO::StatJob *sj = static_cast( job ); if( sj->error() ) { debug() << "STAT ERROR ON" << sj->url() << sj->errorString(); } else { KIO::UDSEntry entry = sj->statResult(); slotEntries( static_cast( job ), KIO::UDSEntryList() << entry ); } sj->deleteLater(); if( m_jobCount <= 0 ) { //emit newResultReady( list ); debug() << "ALL JOBS DONE< TERMINATING THIS QM" << this; emit done(); } } void UpnpQueryMakerInternal::handleArtists( const KIO::UDSEntryList &list ) { Meta::ArtistList ret; foreach( const KIO::UDSEntry &entry, list ) { if( entry.stringValue( KIO::UPNP_CLASS ) == "object.container.person.musicArtist" ) { debug() << this << "ARTIST" << entry.stringValue( KIO::UDSEntry::UDS_DISPLAY_NAME ); ret << m_collection->cache()->getArtist( entry.stringValue( KIO::UDSEntry::UDS_DISPLAY_NAME ) ); } else { if( entry.contains( KIO::UPNP_ARTIST ) ) { ret << m_collection->cache()->getArtist( entry.stringValue( KIO::UPNP_ARTIST ) ); } else { runStat( entry.stringValue( KIO::UPNP_ID ) ); } } } emit newResultReady( ret ); } void UpnpQueryMakerInternal::handleAlbums( const KIO::UDSEntryList &list ) { DEBUG_BLOCK debug() << "HANDLING ALBUMS" << list.length(); Meta::AlbumList ret; foreach( const KIO::UDSEntry &entry, list ) { if( entry.stringValue( KIO::UPNP_CLASS ) == "object.container.album.musicAlbum" ) { debug() << this << "ALBUM" << entry.stringValue( KIO::UDSEntry::UDS_DISPLAY_NAME ) << entry.stringValue(KIO::UPNP_ARTIST); ret << m_collection->cache()->getAlbum( entry.stringValue( KIO::UDSEntry::UDS_DISPLAY_NAME ), entry.stringValue( KIO::UPNP_ARTIST ) ); } else { if( entry.contains( KIO::UPNP_ALBUM ) ) { ret << m_collection->cache()->getAlbum( entry.stringValue( KIO::UPNP_ALBUM ), entry.stringValue( KIO::UPNP_ARTIST ) ); } else { runStat( entry.stringValue( KIO::UPNP_ID ) ); } } } emit newResultReady( ret ); } void UpnpQueryMakerInternal::handleTracks( const KIO::UDSEntryList &list ) { DEBUG_BLOCK debug() << "HANDLING TRACKS" << list.length(); Meta::TrackList ret; foreach( const KIO::UDSEntry &entry, list ) { // If we did a list job with an attempt to check the cache (ie. no meta-data requested from server ) // we might have an incomplete cache entry for the track. // if we have an incomplete entry, we queue a stat job which fetches // the entry. Now this stat job is going to call handleTracks again // When called from a StatJob, we want to fill up the cache // with valid values. // So if the cache entry is incomplete, but the UDSEntry is complete // set the refresh option to true when calling getTrack Meta::TrackPtr track = m_collection->cache()->getTrack( entry ); if( track->playableUrl().isEmpty() ) { debug() << "TRACK HAS INCOMPLETE ENTRY" << track->name() << track->album()->name(); if( !entry.stringValue( KIO::UDSEntry::UDS_TARGET_URL ).isEmpty() ) { debug() << "GOT TRACK DETAILS FROM STAT JOB"; // reached from a StatJob // fill up valid values AND add this track to ret since it is now valid track = m_collection->cache()->getTrack( entry, true ); debug() << "NOW TRACK DETAILS ARE" << track->name() << track->album()->name(); } else { // start a StatJob, but DON'T insert this incomplete entry into ret debug() << "FETCHING COMPLETE TRACK DATA" << track->name(); runStat( entry.stringValue( KIO::UPNP_ID ) ); continue; } } ret << m_collection->cache()->getTrack( entry ); } emit newResultReady( ret ); } void UpnpQueryMakerInternal::handleCustom( const KIO::UDSEntryList &list ) { emit newResultReady( list ); } } diff --git a/src/core-impl/collections/upnpcollection/UpnpQueryMakerInternal.h b/src/core-impl/collections/upnpcollection/UpnpQueryMakerInternal.h index 03154a4758..9fe20dc048 100644 --- a/src/core-impl/collections/upnpcollection/UpnpQueryMakerInternal.h +++ b/src/core-impl/collections/upnpcollection/UpnpQueryMakerInternal.h @@ -1,82 +1,82 @@ /* Copyright (C) 2010 Nikhil Marathe 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef UPNPQUERYMAKERINTERNAL_H #define UPNPQUERYMAKERINTERNAL_H #include -#include +#include #include #include "core/collections/QueryMaker.h" class KJob; namespace KIO { class Job; class SimpleJob; } namespace Collections { class UpnpSearchCollection; class UpnpQueryMakerInternal : public QObject { Q_OBJECT public: UpnpQueryMakerInternal( UpnpSearchCollection *collection ); ~UpnpQueryMakerInternal(); void setQueryType( Collections::QueryMaker::QueryType type ) { m_queryType = type; } void reset(); - void runQuery( KUrl query, bool filter=true ); + void runQuery( QUrl query, bool filter=true ); signals: void results( bool error, const KIO::UDSEntryList list ); void done(); void newResultReady( Meta::TrackList ); void newResultReady( Meta::ArtistList ); void newResultReady( Meta::AlbumList ); void newResultReady( Meta::GenreList ); void newResultReady( const KIO::UDSEntryList & ); private slots: void slotEntries( KIO::Job *, const KIO::UDSEntryList & ); void slotDone( KJob * ); void slotStatDone( KJob * ); private: void handleArtists( const KIO::UDSEntryList &list ); void handleAlbums( const KIO::UDSEntryList &list ); void handleTracks( const KIO::UDSEntryList &list ); void handleCustom( const KIO::UDSEntryList &list ); void queueJob( KIO::SimpleJob *job ); void runStat( const QString &id ); private: UpnpSearchCollection *m_collection; QueryMaker::QueryType m_queryType; int m_jobCount; }; } #endif // UPNPQUERYMAKERINTERNAL_H diff --git a/src/core-impl/collections/upnpcollection/UpnpSearchCollection.cpp b/src/core-impl/collections/upnpcollection/UpnpSearchCollection.cpp index c3b703a106..9b1058281c 100644 --- a/src/core-impl/collections/upnpcollection/UpnpSearchCollection.cpp +++ b/src/core-impl/collections/upnpcollection/UpnpSearchCollection.cpp @@ -1,83 +1,83 @@ /**************************************************************************************** * Copyright (c) 2010 Nikhil Marathe * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "UpnpSearchCollection" #include "UpnpSearchCollection.h" #include "core/support/Debug.h" #include "UpnpQueryMaker.h" #include "UpnpMeta.h" #include "UpnpCache.h" #include #include #include #include #include #include "upnptypes.h" #include #include using namespace Meta; namespace Collections { //UpnpSearchCollection // TODO register for the device bye bye and emit remove() UpnpSearchCollection::UpnpSearchCollection( const DeviceInfo& dev, QStringList searchCapabilities ) : UpnpCollectionBase( dev ) , m_searchCapabilities( searchCapabilities ) , m_cache( new UpnpCache( this ) ) { DEBUG_BLOCK OrgKdeKDirNotifyInterface *notify = new OrgKdeKDirNotifyInterface("", "", QDBusConnection::sessionBus(), this ); connect( notify, SIGNAL(FilesChanged(QStringList)), SLOT(slotFilesChanged(QStringList)) ); } UpnpSearchCollection::~UpnpSearchCollection() { } void UpnpSearchCollection::slotFilesChanged(const QStringList &list ) { debug() << "Files changed" << list; } QueryMaker* UpnpSearchCollection::queryMaker() { DEBUG_BLOCK; return new UpnpQueryMaker( this ); } Meta::TrackPtr -UpnpSearchCollection::trackForUrl( const KUrl &url ) +UpnpSearchCollection::trackForUrl( const QUrl &url ) { #ifdef __GNUC__ #warning Implement track for url #endif // TODO FIXME how to do this? debug() << "Requested track " << url; return Collection::trackForUrl( url ); } } //~ namespace diff --git a/src/core-impl/collections/upnpcollection/UpnpSearchCollection.h b/src/core-impl/collections/upnpcollection/UpnpSearchCollection.h index ea1180197f..56750769ad 100644 --- a/src/core-impl/collections/upnpcollection/UpnpSearchCollection.h +++ b/src/core-impl/collections/upnpcollection/UpnpSearchCollection.h @@ -1,73 +1,73 @@ /**************************************************************************************** * Copyright (c) 2010 Nikhil Marathe * * * * 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, see . * ****************************************************************************************/ #ifndef UPNPSEARCHCOLLECTION_H #define UPNPSEARCHCOLLECTION_H #include "UpnpCollectionBase.h" #include #include #include #include #include #include #include #include namespace KIO { class Job; class ListJob; } class KJob; class QTimer; namespace Collections { class UpnpQueryMaker; class UpnpQueryMakerInternal; class UpnpCache; class UpnpSearchCollection : public UpnpCollectionBase { Q_OBJECT public: UpnpSearchCollection( const DeviceInfo&, QStringList searchCapabilities ); virtual ~UpnpSearchCollection(); virtual QueryMaker* queryMaker(); virtual KIcon icon() const { return KIcon("network-server"); } - Meta::TrackPtr trackForUrl( const KUrl &url ); + Meta::TrackPtr trackForUrl( const QUrl &url ); UpnpCache* cache() { return m_cache; } QStringList searchCapabilities() { return m_searchCapabilities; } private slots: void slotFilesChanged(const QStringList &); private: QStringList m_searchCapabilities; UpnpCache *m_cache; friend class UpnpQueryMakerInternal; }; } //namespace Collections #endif diff --git a/src/core-impl/meta/cue/CueFileSupport.cpp b/src/core-impl/meta/cue/CueFileSupport.cpp index 2736f9de4a..623569d133 100644 --- a/src/core-impl/meta/cue/CueFileSupport.cpp +++ b/src/core-impl/meta/cue/CueFileSupport.cpp @@ -1,463 +1,463 @@ /**************************************************************************************** * Copyright (c) 2009 Nikolaj Hald Nielsen * * * * 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 Pulic License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #include "CueFileSupport.h" #include "core/support/Debug.h" #include "core-impl/meta/timecode/TimecodeMeta.h" #include #include #include #include using namespace MetaCue; /** * Parses a cue sheet file into CueFileItems and inserts them in a QMap * @return a map of CueFileItems. If the cue file was not successfully loaded * the map is empty. * @author (C) 2005 by Martin Ehmke */ -CueFileItemMap CueFileSupport::loadCueFile( const KUrl &cuefile, const Meta::TrackPtr track ) +CueFileItemMap CueFileSupport::loadCueFile( const QUrl &cuefile, const Meta::TrackPtr track ) { return loadCueFile( cuefile, track->playableUrl(), track->length() ); } -CueFileItemMap CueFileSupport::loadCueFile( const KUrl &cuefile, const KUrl &trackUrl, qint64 trackLen ) +CueFileItemMap CueFileSupport::loadCueFile( const QUrl &cuefile, const QUrl &trackUrl, qint64 trackLen ) { DEBUG_BLOCK CueFileItemMap cueItems; debug() << "CUEFILE: " << cuefile.pathOrUrl(); if ( QFile::exists ( cuefile.pathOrUrl() ) ) { debug() << " EXISTS!"; QFile file ( cuefile.pathOrUrl() ); int trackNr = 0; QString defaultArtist; QString defaultAlbum; QString artist; QString title; long length = 0; long prevIndex = -1; bool index00Present = false; long index = -1; bool filesSection = false; bool fileFound = false; int mode = BEGIN; if ( file.open ( QIODevice::ReadOnly ) ) { QTextStream stream ( &file ); QString line; KEncodingProber prober; KEncodingProber::ProberState result = prober.feed( file.readAll() ); file.seek( 0 ); if( result != KEncodingProber::NotMe ) stream.setCodec( QTextCodec::codecForName( prober.encoding() ) ); debug() << "Encoding: " << prober.encoding(); while ( !stream.atEnd() ) { line = stream.readLine().simplified(); if ( line.startsWith ( "title", Qt::CaseInsensitive ) ) { title = line.mid ( 6 ).remove ( '"' ); if ( mode == BEGIN && !filesSection ) { defaultAlbum = title; title.clear(); debug() << "Album: " << defaultAlbum; } else if( !fileFound ) { title.clear(); continue; } else debug() << "Title: " << title; } else if ( line.startsWith ( "performer", Qt::CaseInsensitive ) ) { artist = line.mid ( 10 ).remove ( '"' ); if ( mode == BEGIN && !filesSection ) { defaultArtist = artist; artist.clear(); debug() << "Album Artist: " << defaultArtist; } else if( !fileFound ) { artist.clear(); continue; } else debug() << "Artist: " << artist; } else if ( line.startsWith ( "track", Qt::CaseInsensitive ) && fileFound ) { if ( mode == TRACK_FOUND ) { // not valid, because we have to have an index for the previous track file.close(); debug() << "Mode is TRACK_FOUND, abort."; return CueFileItemMap(); } else if ( mode == INDEX_FOUND ) { if ( artist.isNull() ) artist = defaultArtist; debug() << "Inserting item: " << title << " - " << artist << " on " << defaultAlbum << " (" << trackNr << ")"; // add previous entry to map cueItems.insert ( index, CueFileItem ( title, artist, defaultAlbum, trackNr, index ) ); prevIndex = index; title.clear(); artist.clear(); trackNr = 0; } trackNr = line.section ( ' ',1,1 ).toInt(); debug() << "Track: " << trackNr; mode = TRACK_FOUND; } else if ( line.startsWith ( "index", Qt::CaseInsensitive ) && fileFound ) { if ( mode == TRACK_FOUND ) { int indexNo = line.section ( ' ',1,1 ).toInt(); if ( indexNo == 1 ) { QStringList time = line.section ( ' ', -1, -1 ).split ( ':' ); index = time[0].toLong() *60*1000 + time[1].toLong() *1000 + time[2].toLong() *1000/75; //75 frames per second if ( prevIndex != -1 && !index00Present ) // set the prev track's length if there is INDEX01 present, but no INDEX00 { length = index - prevIndex; debug() << "Setting length of track " << cueItems[prevIndex].title() << " to " << length << " msecs."; cueItems[prevIndex].setLength ( length ); } index00Present = false; mode = INDEX_FOUND; length = 0; } else if ( indexNo == 0 ) // gap, use to calc prev track length { QStringList time = line.section ( ' ', -1, -1 ).split ( ':' ); length = time[0].toLong() * 60 * 1000 + time[1].toLong() * 1000 + time[2].toLong() *1000/75; //75 frames per second if ( prevIndex != -1 ) { length -= prevIndex; //this[prevIndex].getIndex(); debug() << "Setting length of track " << cueItems[prevIndex].title() << " to " << length << " msecs."; cueItems[prevIndex].setLength ( length ); index00Present = true; } else length = 0; } else { debug() << "Skipping unsupported INDEX " << indexNo; } } else { // not valid, because we don't have an associated track file.close(); debug() << "Mode is not TRACK_FOUND but encountered INDEX, abort."; return CueFileItemMap(); } debug() << "index: " << index; } else if( line.startsWith ( "file", Qt::CaseInsensitive ) ) { QString file = line.mid ( 5 ).remove ( '"' ); if( fileFound ) break; fileFound = file.contains ( trackUrl.fileName(), Qt::CaseInsensitive ); filesSection = true; } } if ( artist.isNull() ) artist = defaultArtist; debug() << "Inserting item: " << title << " - " << artist << " on " << defaultAlbum << " (" << trackNr << ")"; // add previous entry to map cueItems.insert ( index, CueFileItem ( title, artist, defaultAlbum, trackNr, index ) ); file.close(); } /** * Because there is no way to set the length for the last track in a normal way, * we have to do some magic here. Having the total length of the media file given * we can set the lenth for the last track after all the cue file was loaded into array. */ cueItems[index].setLength ( trackLen - index ); debug() << "Setting length of track " << cueItems[index].title() << " to " << trackLen - index << " msecs."; return cueItems; } return CueFileItemMap(); } -KUrl CueFileSupport::locateCueSheet ( const KUrl &trackurl ) +QUrl CueFileSupport::locateCueSheet ( const QUrl &trackurl ) { if ( !trackurl.isValid() || !trackurl.isLocalFile() ) - return KUrl(); + return QUrl(); // look for the cue file that matches the media file QString path = trackurl.path(); QString cueFile = path.left ( path.lastIndexOf ( '.' ) ) + ".cue"; if ( validateCueSheet ( cueFile ) ) { debug() << "[CUEFILE]: " << cueFile << " - Shoot blindly, found and loaded. "; - return KUrl ( cueFile ); + return QUrl ( cueFile ); } debug() << "[CUEFILE]: " << cueFile << " - Shoot blindly and missed, searching for other cue files."; bool foundCueFile = false; - QDir dir ( trackurl.directory() ); + QDir dir ( trackurl.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path() ); QStringList filters; filters << "*.cue" << "*.CUE"; dir.setNameFilters ( filters ); QStringList cueFilesList = dir.entryList(); if ( !cueFilesList.empty() ) for ( QStringList::Iterator it = cueFilesList.begin(); it != cueFilesList.end() && !foundCueFile; ++it ) { QFile file ( dir.filePath ( *it ) ); if ( file.open ( QIODevice::ReadOnly ) ) { debug() << "[CUEFILE]: " << *it << " - Opened, looking for the matching FILE stanza." << endl; QTextStream stream ( &file ); QString line; while ( !stream.atEnd() && !foundCueFile ) { line = stream.readLine().simplified(); if ( line.startsWith ( "file", Qt::CaseInsensitive ) ) { line = line.mid ( 5 ).remove ( '"' ); if ( line.contains ( trackurl.fileName(), Qt::CaseInsensitive ) ) { cueFile = dir.filePath ( *it ); if ( validateCueSheet ( cueFile ) ) { debug() << "[CUEFILE]: " << cueFile << " - Looked inside cue files, found and loaded proper one" << endl; foundCueFile = true; } } } } file.close(); } } if ( foundCueFile ) - return KUrl ( cueFile ); + return QUrl ( cueFile ); debug() << "[CUEFILE]: - Didn't find any matching cue file." << endl; - return KUrl(); + return QUrl(); } bool CueFileSupport::validateCueSheet ( const QString& cuefile ) { if ( !QFile::exists ( cuefile ) ) return false; QFile file ( cuefile ); int track = 0; QString defaultArtist; QString defaultAlbum; QString artist; QString title; long length = 0; long prevIndex = -1; bool index00Present = false; long index = -1; int mode = BEGIN; if ( file.open ( QIODevice::ReadOnly ) ) { QTextStream stream ( &file ); QString line; while ( !stream.atEnd() ) { line = stream.readLine().simplified(); if ( line.startsWith ( "title", Qt::CaseInsensitive ) ) { title = line.mid ( 6 ).remove ( '"' ); if ( mode == BEGIN ) { defaultAlbum = title; title.clear(); debug() << "Album: " << defaultAlbum; } else debug() << "Title: " << title; } else if ( line.startsWith ( "performer", Qt::CaseInsensitive ) ) { artist = line.mid ( 10 ).remove ( '"' ); if ( mode == BEGIN ) { defaultArtist = artist; artist.clear(); debug() << "Album Artist: " << defaultArtist; } else debug() << "Artist: " << artist; } else if ( line.startsWith ( "track", Qt::CaseInsensitive ) ) { if ( mode == TRACK_FOUND ) { // not valid, because we have to have an index for the previous track file.close(); debug() << "Mode is TRACK_FOUND, abort."; return false; } if ( mode == INDEX_FOUND ) { if ( artist.isNull() ) artist = defaultArtist; prevIndex = index; title.clear(); artist.clear(); track = 0; } track = line.section ( ' ',1,1 ).toInt(); debug() << "Track: " << track; mode = TRACK_FOUND; } else if ( line.startsWith ( "index", Qt::CaseInsensitive ) ) { if ( mode == TRACK_FOUND ) { int indexNo = line.section ( ' ',1,1 ).toInt(); if ( indexNo == 1 ) { QStringList time = line.section ( ' ', -1, -1 ).split ( ':' ); index = time[0].toLong() *60*1000 + time[1].toLong() *1000 + time[2].toLong() *1000/75; //75 frames per second if ( prevIndex != -1 && !index00Present ) // set the prev track's length if there is INDEX01 present, but no INDEX00 { length = index - prevIndex; } index00Present = false; mode = INDEX_FOUND; length = 0; } else if ( indexNo == 0 ) // gap, use to calc prev track length { QStringList time = line.section ( ' ', -1, -1 ).split ( ':' ); length = time[0].toLong() *60*1000 + time[1].toLong() *1000 + time[2].toLong() *1000/75; //75 frames per second if ( prevIndex != -1 ) { length -= prevIndex; //this[prevIndex].getIndex(); index00Present = true; } else length = 0; } else { debug() << "Skipping unsupported INDEX " << indexNo; } } else { // not valid, because we don't have an associated track file.close(); debug() << "Mode is not TRACK_FOUND but encountered INDEX, abort."; return false; } debug() << "index: " << index; } } if( mode == BEGIN ) { file.close(); debug() << "Cue file is invalid"; return false; } if ( artist.isNull() ) artist = defaultArtist; file.close(); } return true; } Meta::TrackList CueFileSupport::generateTimeCodeTracks( Meta::TrackPtr baseTrack, CueFileItemMap itemMap ) { Meta::TrackList trackList; foreach( const CueFileItem &item, itemMap ) { Meta::TimecodeTrack *track = new Meta::TimecodeTrack( item.title(), baseTrack->playableUrl().url(), item.index(), item.index() + item.length() ); track->beginUpdate(); track->setArtist( item.artist() ); track->setAlbum( item.album() ); track->setTrackNumber( item.trackNumber() ); track->endUpdate(); trackList << Meta::TrackPtr( track ); } return trackList; } diff --git a/src/core-impl/meta/cue/CueFileSupport.h b/src/core-impl/meta/cue/CueFileSupport.h index e8699ccf4f..6225eb74e0 100644 --- a/src/core-impl/meta/cue/CueFileSupport.h +++ b/src/core-impl/meta/cue/CueFileSupport.h @@ -1,130 +1,130 @@ /**************************************************************************************** * Copyright (c) 2009 Nikolaj Hald Nielsen * * * * 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 Pulic License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #ifndef CUEFILESUPPORT_H #define CUEFILESUPPORT_H #include "core/meta/forward_declarations.h" -#include +#include #include #include #include namespace MetaCue { class CueFileItem { public: CueFileItem ( const QString& title, const QString& artist, const QString& album, const int trackNumber, const long index ) : m_title ( title ) , m_artist ( artist ) , m_album ( album ) , m_trackNumber ( trackNumber ) , m_index ( index ) , m_length ( -1 ) {} CueFileItem() : m_title( ) , m_artist( ) , m_album( ) , m_trackNumber ( -1 ) , m_index ( -1 ) , m_length ( -1 ) {} void setLength ( const long length ) { m_length = length; } const QString title () const { return m_title; } const QString artist () const { return m_artist; } const QString album () const { return m_album; } int trackNumber () const { return m_trackNumber; } long index () const { return m_index; } long length () const { return m_length; } private: QString m_title; QString m_artist; QString m_album; int m_trackNumber; long m_index; long m_length; //QSet observers; - KUrl m_url; + QUrl m_url; }; typedef QMap CueFileItemMap; class CueFileSupport { public: enum Markers { BEGIN = 0, TRACK_FOUND, // track found, index not yet found INDEX_FOUND }; - static CueFileItemMap loadCueFile( const KUrl &cuefile, const Meta::TrackPtr track ); - static CueFileItemMap loadCueFile( const KUrl &cuefile, const KUrl &trackUrl, qint64 trackLen ); + static CueFileItemMap loadCueFile( const QUrl &cuefile, const Meta::TrackPtr track ); + static CueFileItemMap loadCueFile( const QUrl &cuefile, const QUrl &trackUrl, qint64 trackLen ); /** * Used to locate a cue sheet for a local track. - * @return A KUrl containing the url for the cue sheet + * @return A QUrl containing the url for the cue sheet * if a valid one was located */ - static KUrl locateCueSheet ( const KUrl &trackurl ); + static QUrl locateCueSheet ( const QUrl &trackurl ); /** * Attempts to load and parse a cue sheet. * @return true if the cue sheet is valid * false if the cue sheet is invalid */ static bool validateCueSheet ( const QString& cuefile ); static Meta::TrackList generateTimeCodeTracks( Meta::TrackPtr baseTrack, CueFileItemMap itemMap ); }; } #endif // CUEFILESUPPORT_H diff --git a/src/core-impl/meta/file/File.cpp b/src/core-impl/meta/file/File.cpp index 993b60a009..06d147fc4c 100644 --- a/src/core-impl/meta/file/File.cpp +++ b/src/core-impl/meta/file/File.cpp @@ -1,598 +1,598 @@ /**************************************************************************************** * Copyright (c) 2007 Maximilian Kossick * * Copyright (c) 2008 Seb Ruiz * * Copyright (c) 2012 MatÄ›j Laitl * * * * 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, see . * ****************************************************************************************/ #include "File.h" #include "File_p.h" #include #ifdef HAVE_LIBLASTFM #include "LastfmReadLabelCapability.h" #endif #include "MainWindow.h" #include "amarokurls/BookmarkMetaActions.h" #include "amarokurls/PlayUrlRunner.h" #include "browsers/filebrowser/FileBrowser.h" #include "core/capabilities/BookmarkThisCapability.h" #include "core/capabilities/FindInSourceCapability.h" #include "core/meta/Meta.h" #include "core/meta/support/MetaUtility.h" #include "core/playlists/PlaylistFormat.h" #include "core/support/Amarok.h" #include "core-impl/capabilities/timecode/TimecodeWriteCapability.h" #include "core-impl/capabilities/timecode/TimecodeLoadCapability.h" #include "core-impl/support/UrlStatisticsStore.h" #include #include #include #include #include #include #include using namespace MetaFile; class TimecodeWriteCapabilityImpl : public Capabilities::TimecodeWriteCapability { public: TimecodeWriteCapabilityImpl( MetaFile::Track *track ) : Capabilities::TimecodeWriteCapability() , m_track( track ) {} virtual bool writeTimecode ( qint64 miliseconds ) { DEBUG_BLOCK return Capabilities::TimecodeWriteCapability::writeTimecode( miliseconds, Meta::TrackPtr( m_track.data() ) ); } virtual bool writeAutoTimecode ( qint64 miliseconds ) { DEBUG_BLOCK return Capabilities::TimecodeWriteCapability::writeAutoTimecode( miliseconds, Meta::TrackPtr( m_track.data() ) ); } private: KSharedPtr m_track; }; class TimecodeLoadCapabilityImpl : public Capabilities::TimecodeLoadCapability { public: TimecodeLoadCapabilityImpl( MetaFile::Track *track ) : Capabilities::TimecodeLoadCapability() , m_track( track ) {} virtual bool hasTimecodes() { if ( loadTimecodes().size() > 0 ) return true; return false; } virtual BookmarkList loadTimecodes() { BookmarkList list = PlayUrlRunner::bookmarksFromUrl( m_track->playableUrl() ); return list; } private: KSharedPtr m_track; }; class FindInSourceCapabilityImpl : public Capabilities::FindInSourceCapability { public: FindInSourceCapabilityImpl( MetaFile::Track *track ) : Capabilities::FindInSourceCapability() , m_track( track ) {} virtual void findInSource( QFlags tag ) { Q_UNUSED( tag ) //first show the filebrowser AmarokUrl url; url.setCommand( "navigate" ); url.setPath( "files" ); url.run(); //then navigate to the correct directory BrowserCategory * fileCategory = The::mainWindow()->browserDock()->list()->activeCategoryRecursive(); if( fileCategory ) { FileBrowser * fileBrowser = dynamic_cast( fileCategory ); if( fileBrowser ) { //get the path of the parent directory of the file - KUrl playableUrl = m_track->playableUrl(); - fileBrowser->setDir( playableUrl.directory() ); + QUrl playableUrl = m_track->playableUrl(); + fileBrowser->setDir( playableUrl.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path() ); } } } private: KSharedPtr m_track; }; -Track::Track( const KUrl &url ) +Track::Track( const QUrl &url ) : Meta::Track() , d( new Track::Private( this ) ) { d->url = url; d->readMetaData(); d->album = Meta::AlbumPtr( new MetaFile::FileAlbum( d ) ); d->artist = Meta::ArtistPtr( new MetaFile::FileArtist( d ) ); d->albumArtist = Meta::ArtistPtr( new MetaFile::FileArtist( d, true ) ); d->genre = Meta::GenrePtr( new MetaFile::FileGenre( d ) ); d->composer = Meta::ComposerPtr( new MetaFile::FileComposer( d ) ); d->year = Meta::YearPtr( new MetaFile::FileYear( d ) ); } Track::~Track() { delete d; } QString Track::name() const { if( d ) { const QString trackName = d->m_data.title; return trackName; } return "This is a bug!"; } -KUrl +QUrl Track::playableUrl() const { return d->url; } QString Track::prettyUrl() const { if(d->url.isLocalFile()) { return d->url.toLocalFile(); } else { return d->url.path(); } } QString Track::uidUrl() const { return d->url.url(); } QString Track::notPlayableReason() const { return localFileNotPlayableReason( playableUrl().toLocalFile() ); } bool Track::isEditable() const { QFileInfo info = QFileInfo( playableUrl().pathOrUrl() ); return info.isFile() && info.isWritable(); } Meta::AlbumPtr Track::album() const { return d->album; } Meta::ArtistPtr Track::artist() const { return d->artist; } Meta::GenrePtr Track::genre() const { return d->genre; } Meta::ComposerPtr Track::composer() const { return d->composer; } Meta::YearPtr Track::year() const { return d->year; } void Track::setAlbum( const QString &newAlbum ) { QWriteLocker locker( &d->lock ); commitIfInNonBatchUpdate( Meta::valAlbum, newAlbum ); } void Track::setAlbumArtist( const QString &newAlbumArtist ) { QWriteLocker locker( &d->lock ); commitIfInNonBatchUpdate( Meta::valAlbumArtist, newAlbumArtist ); } void Track::setArtist( const QString &newArtist ) { QWriteLocker locker( &d->lock ); commitIfInNonBatchUpdate( Meta::valArtist, newArtist ); } void Track::setGenre( const QString &newGenre ) { QWriteLocker locker( &d->lock ); commitIfInNonBatchUpdate( Meta::valGenre, newGenre ); } void Track::setComposer( const QString &newComposer ) { QWriteLocker locker( &d->lock ); commitIfInNonBatchUpdate( Meta::valComposer, newComposer ); } void Track::setYear( int newYear ) { QWriteLocker locker( &d->lock ); commitIfInNonBatchUpdate( Meta::valYear, newYear ); } void Track::setTitle( const QString &newTitle ) { QWriteLocker locker( &d->lock ); commitIfInNonBatchUpdate( Meta::valTitle, newTitle ); } void Track::setBpm( const qreal newBpm ) { QWriteLocker locker( &d->lock ); commitIfInNonBatchUpdate( Meta::valBpm, newBpm ); } qreal Track::bpm() const { const qreal bpm = d->m_data.bpm; return bpm; } QString Track::comment() const { const QString commentName = d->m_data.comment; if( !commentName.isEmpty() ) return commentName; return QString(); } void Track::setComment( const QString& newComment ) { QWriteLocker locker( &d->lock ); commitIfInNonBatchUpdate( Meta::valComment, newComment ); } int Track::trackNumber() const { return d->m_data.trackNumber; } void Track::setTrackNumber( int newTrackNumber ) { QWriteLocker locker( &d->lock ); commitIfInNonBatchUpdate( Meta::valTrackNr, newTrackNumber ); } int Track::discNumber() const { return d->m_data.discNumber; } void Track::setDiscNumber( int newDiscNumber ) { QWriteLocker locker( &d->lock ); commitIfInNonBatchUpdate( Meta::valDiscNr, newDiscNumber ); } qint64 Track::length() const { qint64 length = d->m_data.length; if( length == -2 /*Undetermined*/ ) length = 0; return length; } int Track::filesize() const { return d->m_data.fileSize; } int Track::sampleRate() const { int sampleRate = d->m_data.sampleRate; if( sampleRate == -2 /*Undetermined*/ ) sampleRate = 0; return sampleRate; } int Track::bitrate() const { int bitrate = d->m_data.bitRate; if( bitrate == -2 /*Undetermined*/ ) bitrate = 0; return bitrate; } QDateTime Track::createDate() const { if( d->m_data.created > 0 ) return QDateTime::fromTime_t(d->m_data.created); else return QDateTime(); } qreal Track::replayGain( Meta::ReplayGainTag mode ) const { switch( mode ) { case Meta::ReplayGain_Track_Gain: return d->m_data.trackGain; case Meta::ReplayGain_Track_Peak: return d->m_data.trackPeak; case Meta::ReplayGain_Album_Gain: return d->m_data.albumGain; case Meta::ReplayGain_Album_Peak: return d->m_data.albumPeak; } return 0.0; } QString Track::type() const { return Amarok::extension( d->url.fileName() ); } bool -Track::isTrack( const KUrl &url ) +Track::isTrack( const QUrl &url ) { // some playlists lay under audio/ mime category, filter them if( Playlists::isPlaylist( url ) ) return false; // accept remote files, it's too slow to check them at this point if( !url.isLocalFile() ) return true; QFileInfo fileInfo( url.toLocalFile() ); if( fileInfo.size() <= 0 ) return false; // We can't play directories if( fileInfo.isDir() ) return false; const QMimeType mimeType = db.mimeTypeForFile( url.toLocalFile() ); const QString name = mimeType.name(); return name.startsWith( "audio/" ) || name.startsWith( "video/" ); } void Track::beginUpdate() { QWriteLocker locker( &d->lock ); d->batchUpdate++; } void Track::endUpdate() { QWriteLocker locker( &d->lock ); Q_ASSERT( d->batchUpdate > 0 ); d->batchUpdate--; commitIfInNonBatchUpdate(); } bool Track::inCollection() const { return d->collection; // calls QWeakPointer's (bool) operator } Collections::Collection* Track::collection() const { return d->collection.data(); } void Track::setCollection( Collections::Collection *newCollection ) { d->collection = newCollection; } bool Track::hasCapabilityInterface( Capabilities::Capability::Type type ) const { bool readlabel = false; #ifdef HAVE_LIBLASTFM readlabel = true; #endif return type == Capabilities::Capability::BookmarkThis || type == Capabilities::Capability::WriteTimecode || type == Capabilities::Capability::LoadTimecode || ( type == Capabilities::Capability::ReadLabel && readlabel ) || type == Capabilities::Capability::FindInSource; } Capabilities::Capability* Track::createCapabilityInterface( Capabilities::Capability::Type type ) { switch( type ) { case Capabilities::Capability::BookmarkThis: return new Capabilities::BookmarkThisCapability( new BookmarkCurrentTrackPositionAction( 0 ) ); case Capabilities::Capability::WriteTimecode: return new TimecodeWriteCapabilityImpl( this ); case Capabilities::Capability::LoadTimecode: return new TimecodeLoadCapabilityImpl( this ); case Capabilities::Capability::FindInSource: return new FindInSourceCapabilityImpl( this ); #ifdef HAVE_LIBLASTFM case Capabilities::Capability::ReadLabel: if( !d->readLabelCapability ) d->readLabelCapability = new Capabilities::LastfmReadLabelCapability( this ); #endif default: // fall-through return 0; } } Meta::TrackEditorPtr Track::editor() { return Meta::TrackEditorPtr( isEditable() ? this : 0 ); } Meta::StatisticsPtr Track::statistics() { return Meta::StatisticsPtr( this ); } double Track::score() const { return d->m_data.score; } void Track::setScore( double newScore ) { QWriteLocker locker( &d->lock ); commitIfInNonBatchUpdate( Meta::valScore, newScore ); } int Track::rating() const { return d->m_data.rating; } void Track::setRating( int newRating ) { QWriteLocker locker( &d->lock ); commitIfInNonBatchUpdate( Meta::valRating, newRating ); } int Track::playCount() const { return d->m_data.playCount; } void Track::setPlayCount( int newPlayCount ) { QWriteLocker locker( &d->lock ); commitIfInNonBatchUpdate( Meta::valPlaycount, newPlayCount ); } QImage Track::getEmbeddedCover() const { if( d->m_data.embeddedImage ) return Meta::Tag::embeddedCover( d->url.path() ); return QImage(); } void Track::commitIfInNonBatchUpdate( qint64 field, const QVariant &value ) { d->changes.insert( field, value ); commitIfInNonBatchUpdate(); } void Track::commitIfInNonBatchUpdate() { static const QSet statFields = ( QSet() << Meta::valFirstPlayed << Meta::valLastPlayed << Meta::valPlaycount << Meta::valScore << Meta::valRating ); if( d->batchUpdate > 0 || d->changes.isEmpty() ) return; // special case (shortcut) when writing statistics is disabled if( !AmarokConfig::writeBackStatistics() && (QSet::fromList( d->changes.keys() ) - statFields).isEmpty() ) { d->changes.clear(); return; } d->writeMetaData(); // clears d->chages d->lock.unlock(); // rather call notifyObservers() without a lock notifyObservers(); d->lock.lockForWrite(); // return to original state } #include "File_p.moc" diff --git a/src/core-impl/meta/file/File.h b/src/core-impl/meta/file/File.h index ee2a6bbf2f..93ca59e48c 100644 --- a/src/core-impl/meta/file/File.h +++ b/src/core-impl/meta/file/File.h @@ -1,137 +1,137 @@ /**************************************************************************************** * Copyright (c) 2007 Maximilian Kossick * * * * 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, see . * ****************************************************************************************/ #ifndef AMAROK_META_FILE_H #define AMAROK_META_FILE_H #include "amarok_export.h" #include "core/meta/Meta.h" #include "core/meta/Statistics.h" #include "core/meta/TrackEditor.h" namespace MetaFile { class Track; typedef KSharedPtr TrackPtr; class AMAROK_EXPORT Track : public Meta::Track, public Meta::Statistics, Meta::TrackEditor { public: - Track( const KUrl &url ); + Track( const QUrl &url ); virtual ~Track(); //methods inherited from Meta::Base virtual QString name() const; //methods inherited from Meta::Track - virtual KUrl playableUrl() const; + virtual QUrl playableUrl() const; virtual QString prettyUrl() const; virtual QString uidUrl() const; virtual QString notPlayableReason() const; virtual Meta::AlbumPtr album() const; virtual Meta::ArtistPtr artist() const; virtual Meta::GenrePtr genre() const; virtual Meta::ComposerPtr composer() const; virtual Meta::YearPtr year() const; virtual qreal bpm() const; virtual QString comment() const; virtual int trackNumber() const; virtual int discNumber() const; virtual qint64 length() const; virtual int filesize() const; virtual int sampleRate() const; virtual int bitrate() const; virtual QDateTime createDate() const; virtual qreal replayGain( Meta::ReplayGainTag mode ) const; virtual QString type() const; virtual bool inCollection() const; virtual Collections::Collection *collection() const; virtual bool hasCapabilityInterface( Capabilities::Capability::Type type ) const; virtual Capabilities::Capability* createCapabilityInterface( Capabilities::Capability::Type type ); virtual Meta::TrackEditorPtr editor(); virtual Meta::StatisticsPtr statistics(); // Meta::TrackEditor methods: virtual void setAlbum( const QString &newAlbum ); virtual void setAlbumArtist( const QString &newAlbumArtist ); virtual void setArtist( const QString &newArtist ); virtual void setComposer( const QString &newComposer ); virtual void setGenre( const QString &newGenre ); virtual void setYear( int newYear ); virtual void setTitle( const QString &newTitle ); virtual void setComment( const QString &newComment ); virtual void setTrackNumber( int newTrackNumber ); virtual void setDiscNumber( int newDiscNumber ); virtual void setBpm( const qreal newBpm ); // Meta::Statistics methods: virtual double score() const; virtual void setScore( double newScore ); virtual int rating() const; virtual void setRating( int newRating ); virtual int playCount() const; virtual void setPlayCount( int newPlayCount ); // combined Meta::TrackEditor, Meta::Statistics methods: virtual void beginUpdate(); virtual void endUpdate(); // MetaFile::Track own methods: bool isEditable() const; /** * Return true if file at @param url is a track. * * This method does only basic checking of the mime type and is pretty * optimistic, so it may be possible that is the song is not playable with * current backend even if isTrack() returns true. */ - static bool isTrack( const KUrl &url ); + static bool isTrack( const QUrl &url ); virtual QImage getEmbeddedCover() const; virtual void setCollection( Collections::Collection *newCollection ); // publish method so that it can be called by Private. using Meta::Track::notifyObservers; class Private; private: Private * const d; /** * Must be called at end of every set*() method, with d->lock locked for * writing. Takes care of writing back the fields, re-reading them and * notifying observers. */ void commitIfInNonBatchUpdate( qint64 field, const QVariant &value ); void commitIfInNonBatchUpdate(); }; } #endif diff --git a/src/core-impl/meta/file/FileTrackProvider.cpp b/src/core-impl/meta/file/FileTrackProvider.cpp index 74c2abc199..b452cbabe0 100644 --- a/src/core-impl/meta/file/FileTrackProvider.cpp +++ b/src/core-impl/meta/file/FileTrackProvider.cpp @@ -1,44 +1,44 @@ /**************************************************************************************** * Copyright (c) 2012 MatÄ›j Laitl * * * * 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, see . * ****************************************************************************************/ #include "FileTrackProvider.h" #include "core-impl/meta/file/File.h" FileTrackProvider::FileTrackProvider() { } FileTrackProvider::~FileTrackProvider() { } bool -FileTrackProvider::possiblyContainsTrack( const KUrl &url ) const +FileTrackProvider::possiblyContainsTrack( const QUrl &url ) const { if( !url.isLocalFile() ) return false; return MetaFile::Track::isTrack( url ); } Meta::TrackPtr -FileTrackProvider::trackForUrl( const KUrl &url ) +FileTrackProvider::trackForUrl( const QUrl &url ) { if( !possiblyContainsTrack( url ) ) return Meta::TrackPtr(); return Meta::TrackPtr( new MetaFile::Track( url ) ); } diff --git a/src/core-impl/meta/file/FileTrackProvider.h b/src/core-impl/meta/file/FileTrackProvider.h index 28962cefe5..2304eef1e9 100644 --- a/src/core-impl/meta/file/FileTrackProvider.h +++ b/src/core-impl/meta/file/FileTrackProvider.h @@ -1,40 +1,40 @@ /**************************************************************************************** * Copyright (c) 2012 MatÄ›j Laitl * * * * 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, see . * ****************************************************************************************/ #ifndef FILETRACKPROVIDER_H #define FILETRACKPROVIDER_H #include "amarok_export.h" #include "core/collections/Collection.h" /** * A simple track provider that contructs MetaFile::Tracks for local and * existing urls. (no remote protocols supported, just "file" protocol.) */ class AMAROK_EXPORT FileTrackProvider : public Collections::TrackProvider { public: FileTrackProvider(); virtual ~FileTrackProvider(); - virtual bool possiblyContainsTrack( const KUrl &url ) const; - virtual Meta::TrackPtr trackForUrl( const KUrl &url ); + virtual bool possiblyContainsTrack( const QUrl &url ) const; + virtual Meta::TrackPtr trackForUrl( const QUrl &url ); private: Q_DISABLE_COPY( FileTrackProvider ) }; #endif // FILETRACKPROVIDER_H diff --git a/src/core-impl/meta/file/File_p.h b/src/core-impl/meta/file/File_p.h index 5f8b431dea..99cc76c031 100644 --- a/src/core-impl/meta/file/File_p.h +++ b/src/core-impl/meta/file/File_p.h @@ -1,462 +1,462 @@ /**************************************************************************************** * Copyright (c) 2007-2009 Maximilian Kossick * * Copyright (c) 2008 Peter ZHOU * * Copyright (c) 2008 Seb Ruiz * * * * 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, see . * ****************************************************************************************/ #ifndef AMAROK_META_FILE_P_H #define AMAROK_META_FILE_P_H #include "amarokconfig.h" #include "core/collections/Collection.h" #include "core/support/Debug.h" #include "core/meta/Meta.h" #include "core/meta/support/MetaUtility.h" #include "MetaReplayGain.h" #include "MetaTagLib.h" #include "core-impl/collections/support/jobs/WriteTagsJob.h" #include "core-impl/collections/support/ArtistHelper.h" #include "core-impl/capabilities/AlbumActionsCapability.h" #include "covermanager/CoverCache.h" #include #include #include #include #include #include #include #include namespace Capabilities { class LastfmReadLabelCapability; } namespace MetaFile { //d-pointer implementation struct MetaData { MetaData() : created( 0 ) , discNumber( 0 ) , trackNumber( 0 ) , length( 0 ) , fileSize( 0 ) , sampleRate( 0 ) , bitRate( 0 ) , year( 0 ) , bpm( -1.0 ) , trackGain( 0.0 ) , trackPeak( 0.0 ) , albumGain( 0.0 ) , albumPeak( 0.0 ) , embeddedImage( false ) , rating( 0 ) , score( 0.0 ) , playCount( 0 ) { } QString title; QString artist; QString album; QString albumArtist; QString comment; QString composer; QString genre; uint created; int discNumber; int trackNumber; qint64 length; int fileSize; int sampleRate; int bitRate; int year; qreal bpm; qreal trackGain; qreal trackPeak; qreal albumGain; qreal albumPeak; bool embeddedImage; int rating; double score; int playCount; }; class Track::Private : public QObject { Q_OBJECT public: Private( Track *t ) : QObject() , url() , album() , artist() , albumArtist() , batchUpdate( 0 ) , track( t ) {} public: - KUrl url; + QUrl url; Meta::AlbumPtr album; Meta::ArtistPtr artist; Meta::ArtistPtr albumArtist; Meta::GenrePtr genre; Meta::ComposerPtr composer; Meta::YearPtr year; QWeakPointer readLabelCapability; QWeakPointer collection; /** * Number of current batch operations started by @see beginUpdate() and not * yet ended by @see endUpdate(). Must only be accessed with lock held. */ int batchUpdate; Meta::FieldHash changes; QReadWriteLock lock; void writeMetaData() { DEBUG_BLOCK debug() << "changes:" << changes; if( AmarokConfig::writeBack() ) Meta::Tag::writeTags( url.isLocalFile() ? url.toLocalFile() : url.path(), changes, AmarokConfig::writeBackStatistics() ); changes.clear(); readMetaData(); } void notifyObservers() { track->notifyObservers(); } MetaData m_data; public slots: void readMetaData(); private: TagLib::FileRef getFileRef(); Track *track; }; void Track::Private::readMetaData() { QFileInfo fi( url.isLocalFile() ? url.toLocalFile() : url.path() ); m_data.created = fi.created().toTime_t(); Meta::FieldHash values = Meta::Tag::readTags( fi.absoluteFilePath() ); // (re)set all fields to behave the same as the constructor. E.g. catch even complete // removal of tags etc. MetaData def; // default m_data.title = values.value( Meta::valTitle, def.title ).toString(); m_data.artist = values.value( Meta::valArtist, def.artist ).toString(); m_data.album = values.value( Meta::valAlbum, def.album ).toString(); m_data.albumArtist = values.value( Meta::valAlbumArtist, def.albumArtist ).toString(); m_data.embeddedImage = values.value( Meta::valHasCover, def.embeddedImage ).toBool(); m_data.comment = values.value( Meta::valComment, def.comment ).toString(); m_data.genre = values.value( Meta::valGenre, def.genre ).toString(); m_data.composer = values.value( Meta::valComposer, def.composer ).toString(); m_data.year = values.value( Meta::valYear, def.year ).toInt(); m_data.discNumber = values.value( Meta::valDiscNr, def.discNumber ).toInt(); m_data.trackNumber = values.value( Meta::valTrackNr, def.trackNumber ).toInt(); m_data.bpm = values.value( Meta::valBpm, def.bpm ).toReal(); m_data.bitRate = values.value( Meta::valBitrate, def.bitRate ).toInt(); m_data.length = values.value( Meta::valLength, def.length ).toLongLong(); m_data.sampleRate = values.value( Meta::valSamplerate, def.sampleRate ).toInt(); m_data.fileSize = values.value( Meta::valFilesize, def.fileSize ).toLongLong(); m_data.trackGain = values.value( Meta::valTrackGain, def.trackGain ).toReal(); m_data.trackPeak= values.value( Meta::valTrackGainPeak, def.trackPeak ).toReal(); m_data.albumGain = values.value( Meta::valAlbumGain, def.albumGain ).toReal(); m_data.albumPeak= values.value( Meta::valAlbumGainPeak, def.albumPeak ).toReal(); // only read the stats if we can write them later. Would be annoying to have // read-only rating that you don't like if( AmarokConfig::writeBackStatistics() ) { m_data.rating = values.value( Meta::valRating, def.rating ).toInt(); m_data.score = values.value( Meta::valScore, def.score ).toDouble(); m_data.playCount = values.value( Meta::valPlaycount, def.playCount ).toInt(); } if(url.isLocalFile()) { m_data.fileSize = QFile( url.toLocalFile() ).size(); } else { m_data.fileSize = QFile( url.path() ).size(); } //as a last ditch effort, use the filename as the title if nothing else has been found if ( m_data.title.isEmpty() ) { m_data.title = url.fileName(); } // try to guess best album artist (even if non-empty, part of compilation detection) m_data.albumArtist = ArtistHelper::bestGuessAlbumArtist( m_data.albumArtist, m_data.artist, m_data.genre, m_data.composer ); } // internal helper classes class FileArtist : public Meta::Artist { public: FileArtist( MetaFile::Track::Private *dptr, bool isAlbumArtist = false ) : Meta::Artist() , d( dptr ) , m_isAlbumArtist( isAlbumArtist ) {} Meta::TrackList tracks() { return Meta::TrackList(); } QString name() const { const QString artist = m_isAlbumArtist ? d.data()->m_data.albumArtist : d.data()->m_data.artist; return artist; } bool operator==( const Meta::Artist &other ) const { return name() == other.name(); } QWeakPointer const d; const bool m_isAlbumArtist; }; class FileAlbum : public Meta::Album { public: FileAlbum( MetaFile::Track::Private *dptr ) : Meta::Album() , d( dptr ) {} bool hasCapabilityInterface( Capabilities::Capability::Type type ) const { switch( type ) { case Capabilities::Capability::Actions: return true; default: return false; } } Capabilities::Capability* createCapabilityInterface( Capabilities::Capability::Type type ) { switch( type ) { case Capabilities::Capability::Actions: return new Capabilities::AlbumActionsCapability( Meta::AlbumPtr( this ) ); default: return 0; } } bool isCompilation() const { /* non-compilation albums with no album artists may be hidden in collection * browser if certain modes are used, so force compilation in this case */ return !hasAlbumArtist(); } bool hasAlbumArtist() const { return !d.data()->albumArtist->name().isEmpty(); } Meta::ArtistPtr albumArtist() const { /* only return album artist if it would be non-empty, some Amarok parts do not * call hasAlbumArtist() prior to calling albumArtist() and it is better to be * consistent with other Meta::Track implementations */ if( hasAlbumArtist() ) return d.data()->albumArtist; return Meta::ArtistPtr(); } Meta::TrackList tracks() { return Meta::TrackList(); } QString name() const { if( d ) { const QString albumName = d.data()->m_data.album; return albumName; } else return QString(); } bool hasImage( int /* size */ = 0 ) const { if( d && d.data()->m_data.embeddedImage ) return true; return false; } QImage image( int size = 0 ) const { QImage image; if( d && d.data()->m_data.embeddedImage ) { image = Meta::Tag::embeddedCover( d.data()->url.toLocalFile() ); } if( image.isNull() || size <= 0 /* do not scale */ ) return image; return image.scaled( size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation ); } bool canUpdateImage() const { return d; // true if underlying track is not null } void setImage( const QImage &image ) { if( !d ) return; Meta::FieldHash fields; fields.insert( Meta::valImage, image ); WriteTagsJob *job = new WriteTagsJob( d.data()->url.toLocalFile(), fields ); QObject::connect( job, SIGNAL(done(ThreadWeaver::Job*)), job, SLOT(deleteLater()) ); ThreadWeaver::Weaver::instance()->enqueue( job ); if( d.data()->m_data.embeddedImage == image.isNull() ) // we need to toggle the embeddedImage switch in this case QObject::connect( job, SIGNAL(done(ThreadWeaver::Job*)), d.data(), SLOT(readMetaData()) ); CoverCache::invalidateAlbum( this ); notifyObservers(); // following call calls Track's notifyObservers. This is needed because for example // UmsCollection justifiably listens only to Track's metadataChanged() to update // its MemoryCollection maps d.data()->notifyObservers(); } void removeImage() { setImage( QImage() ); } bool operator==( const Meta::Album &other ) const { return name() == other.name(); } QWeakPointer const d; }; class FileGenre : public Meta::Genre { public: FileGenre( MetaFile::Track::Private *dptr ) : Meta::Genre() , d( dptr ) {} Meta::TrackList tracks() { return Meta::TrackList(); } QString name() const { const QString genreName = d.data()->m_data.genre; return genreName; } bool operator==( const Meta::Genre &other ) const { return name() == other.name(); } QWeakPointer const d; }; class FileComposer : public Meta::Composer { public: FileComposer( MetaFile::Track::Private *dptr ) : Meta::Composer() , d( dptr ) {} Meta::TrackList tracks() { return Meta::TrackList(); } QString name() const { const QString composer = d.data()->m_data.composer; return composer; } bool operator==( const Meta::Composer &other ) const { return name() == other.name(); } QWeakPointer const d; }; class FileYear : public Meta::Year { public: FileYear( MetaFile::Track::Private *dptr ) : Meta::Year() , d( dptr ) {} Meta::TrackList tracks() { return Meta::TrackList(); } QString name() const { const QString year = QString::number( d.data()->m_data.year ); return year; } bool operator==( const Meta::Year &other ) const { return name() == other.name(); } QWeakPointer const d; }; } #endif diff --git a/src/core-impl/meta/multi/MultiTrack.cpp b/src/core-impl/meta/multi/MultiTrack.cpp index e50740acfd..2fe16908c6 100644 --- a/src/core-impl/meta/multi/MultiTrack.cpp +++ b/src/core-impl/meta/multi/MultiTrack.cpp @@ -1,154 +1,154 @@ /**************************************************************************************** * Copyright (c) 2009 Nikolaj Hald Nielsen * * * * 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, see . * ****************************************************************************************/ #include "MultiTrack.h" #include "core/meta/Statistics.h" #include "core-impl/capabilities/multisource/MultiSourceCapabilityImpl.h" using namespace Meta; MultiTrack::MultiTrack( Playlists::PlaylistPtr playlist ) : QObject() , Track() , m_playlist( playlist ) { Q_ASSERT( playlist ); if( playlist->trackCount() < 0 ) { PlaylistObserver::subscribeTo( playlist ); playlist->triggerTrackLoad(); } if( !playlist->tracks().isEmpty() ) setSource( 0 ); } MultiTrack::~MultiTrack() { } QStringList Meta::MultiTrack::sources() const { QStringList trackNames; foreach ( TrackPtr track, m_playlist->tracks() ) { trackNames << track->prettyUrl(); } return trackNames; } void MultiTrack::setSource( int source ) { QWriteLocker locker( &m_lock ); setSourceImpl( source ); locker.unlock(); notifyObservers(); emit urlChanged( playableUrl() ); } int Meta::MultiTrack::current() const { QReadLocker locker( &m_lock ); return m_playlist->tracks().indexOf( m_currentTrack ); } -KUrl +QUrl MultiTrack::nextUrl() const { int index = current() + 1; Meta::TrackPtr track = m_playlist->tracks().value( index ); if( track ) { track->prepareToPlay(); return track->playableUrl(); } - return KUrl(); + return QUrl(); } bool MultiTrack::hasCapabilityInterface(Capabilities::Capability::Type type) const { return type == Capabilities::Capability::MultiSource; } Capabilities::Capability * MultiTrack::createCapabilityInterface(Capabilities::Capability::Type type) { switch( type ) { case Capabilities::Capability::MultiSource: return new Capabilities::MultiSourceCapabilityImpl( this ); default: return 0; } } void MultiTrack::prepareToPlay() { QReadLocker locker( &m_lock ); if( m_currentTrack ) m_currentTrack->prepareToPlay(); } Meta::StatisticsPtr Meta::MultiTrack::statistics() { QReadLocker locker( &m_lock ); return m_currentTrack ? m_currentTrack->statistics() : Track::statistics(); } void Meta::MultiTrack::metadataChanged( Meta::TrackPtr track ) { Q_UNUSED( track ) // forward changes from active tracks notifyObservers(); } void MultiTrack::trackAdded( Playlists::PlaylistPtr, TrackPtr, int ) { PlaylistObserver::unsubscribeFrom( m_playlist ); QWriteLocker locker( &m_lock ); if( !m_currentTrack ) { setSourceImpl( 0 ); locker.unlock(); notifyObservers(); emit urlChanged( playableUrl() ); } } void MultiTrack::setSourceImpl( int source ) { if( source < 0 || source >= m_playlist->tracks().count() ) return; if( m_currentTrack ) Observer::unsubscribeFrom( m_currentTrack ); m_currentTrack = m_playlist->tracks().at( source ); Observer::subscribeTo( m_currentTrack ); } diff --git a/src/core-impl/meta/multi/MultiTrack.h b/src/core-impl/meta/multi/MultiTrack.h index 32cbe828ff..3659f04bc7 100644 --- a/src/core-impl/meta/multi/MultiTrack.h +++ b/src/core-impl/meta/multi/MultiTrack.h @@ -1,108 +1,108 @@ /**************************************************************************************** * Copyright (c) 2009 Nikolaj Hald Nielsen * * * * 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, see . * ****************************************************************************************/ #ifndef METAMULTITRACK_H #define METAMULTITRACK_H #include "core/capabilities/MultiSourceCapability.h" #include "core/meta/Meta.h" #include "core/meta/Observer.h" #include "core/playlists/Playlist.h" #include "core-impl/meta/default/DefaultMetaTypes.h" namespace Meta { /** * A track that wraps a playlist. This is useful, for instance, for adding radio * streams with multiple fallback streams to the playlist as a single item. * * @author Nikolaj Hald Nielsen */ class MultiTrack : public QObject, public Track, private Meta::Observer, private Playlists::PlaylistObserver { Q_OBJECT public: MultiTrack( Playlists::PlaylistPtr playlist ); ~MultiTrack(); QStringList sources() const; void setSource( int source ); int current() const; - KUrl nextUrl() const; + QUrl nextUrl() const; virtual bool hasCapabilityInterface( Capabilities::Capability::Type type ) const; virtual Capabilities::Capability *createCapabilityInterface( Capabilities::Capability::Type type ); // forward lots of stuff: #define FORWARD( Type, method, default ) Type method() const { return m_currentTrack ? m_currentTrack->method() : default; } FORWARD( QString, name, QString() ) FORWARD( QString, prettyName, QString() ) - FORWARD( KUrl, playableUrl, KUrl() ) + FORWARD( QUrl, playableUrl, QUrl() ) FORWARD( QString, prettyUrl, m_playlist->uidUrl().prettyUrl() ) // TODO: change to m_playlist->uidUrl() unconditionally once playlist restorer can cope with it: FORWARD( QString, uidUrl, m_playlist->uidUrl().url() ) FORWARD( QString, notPlayableReason, i18nc( "Reason why a track is not playable", "Underlying playlist is empty" ) ) FORWARD( AlbumPtr, album, AlbumPtr( new DefaultAlbum() ) ); FORWARD( ArtistPtr, artist, ArtistPtr( new DefaultArtist() ) ); FORWARD( ComposerPtr, composer, ComposerPtr( new DefaultComposer() ) ); FORWARD( GenrePtr, genre, GenrePtr( new DefaultGenre() ) ); FORWARD( YearPtr, year, YearPtr( new DefaultYear() ) ); FORWARD( qreal, bpm, -1 ) FORWARD( QString, comment, QString() ) FORWARD( qint64, length, 0 ) FORWARD( int, filesize, 0 ) FORWARD( int, sampleRate, 0 ) FORWARD( int, bitrate, 0 ) FORWARD( int, trackNumber, 0 ) FORWARD( int, discNumber, 0 ) FORWARD( QString, type, QString() ) #undef FORWARD void prepareToPlay(); virtual StatisticsPtr statistics(); signals: - void urlChanged( const KUrl &url ); + void urlChanged( const QUrl &url ); private: using Observer::metadataChanged; virtual void metadataChanged( Meta::TrackPtr track ); using PlaylistObserver::metadataChanged; virtual void trackAdded( Playlists::PlaylistPtr playlist, TrackPtr track, int position ); /** * Implementation for setSource. Must be called with m_lock held for writing. */ void setSourceImpl( int source ); // marked as mutable because many Playlist methods aren't const while they should be mutable Playlists::PlaylistPtr m_playlist; TrackPtr m_currentTrack; /** * Guards access to data members; note that m_playlist methods are considered * thread-safe and the pointer itself does not change throughout life of thhis * object, so mere m_playlist->someMethod() doesn't have to be guarded. */ mutable QReadWriteLock m_lock; }; } #endif diff --git a/src/core-impl/meta/proxy/MetaProxy.cpp b/src/core-impl/meta/proxy/MetaProxy.cpp index 113cae950a..6425dcf97b 100644 --- a/src/core-impl/meta/proxy/MetaProxy.cpp +++ b/src/core-impl/meta/proxy/MetaProxy.cpp @@ -1,525 +1,525 @@ /**************************************************************************************** * Copyright (c) 2007 Maximilian Kossick * * * * 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, see . * ****************************************************************************************/ #include "MetaProxy.h" #include "core/meta/Statistics.h" #include "core-impl/collections/support/CollectionManager.h" #include "core-impl/meta/proxy/MetaProxy_p.h" #include "core-impl/meta/proxy/MetaProxy_p.moc" #include "core-impl/meta/proxy/MetaProxyWorker.h" #include #include #include #include #include #include using namespace MetaProxy; class ProxyArtist; class ProxyFmAlbum; class ProxyGenre; class ProxyComposer; class ProxyYear; -MetaProxy::Track::Track( const KUrl &url, LookupType lookupType ) +MetaProxy::Track::Track( const QUrl &url, LookupType lookupType ) : Meta::Track() , d( new Private() ) { d->url = url; d->proxy = this; d->cachedLength = 0; d->albumPtr = Meta::AlbumPtr( new ProxyAlbum( d ) ); d->artistPtr = Meta::ArtistPtr( new ProxyArtist( d ) ); d->genrePtr = Meta::GenrePtr( new ProxyGenre( d ) ); d->composerPtr = Meta::ComposerPtr( new ProxyComposer( d ) ); d->yearPtr = Meta::YearPtr( new ProxyYear( d ) ); QThread *mainThread = QCoreApplication::instance()->thread(); bool foreignThread = QThread::currentThread() != mainThread; if( foreignThread ) d->moveToThread( mainThread ); if( lookupType == AutomaticLookup ) { Worker *worker = new Worker( d->url ); if( foreignThread ) worker->moveToThread( mainThread ); QObject::connect( worker, SIGNAL(finishedLookup(Meta::TrackPtr)), d, SLOT(slotUpdateTrack(Meta::TrackPtr)) ); ThreadWeaver::Weaver::instance()->enqueue( worker ); } } MetaProxy::Track::~Track() { delete d; } void MetaProxy::Track::lookupTrack( Collections::TrackProvider *provider ) { Worker *worker = new Worker( d->url, provider ); QThread *mainThread = QCoreApplication::instance()->thread(); if( QThread::currentThread() != mainThread ) worker->moveToThread( mainThread ); QObject::connect( worker, SIGNAL(finishedLookup(Meta::TrackPtr)), d, SLOT(slotUpdateTrack(Meta::TrackPtr)) ); ThreadWeaver::Weaver::instance()->enqueue( worker ); } QString MetaProxy::Track::name() const { if( d->realTrack ) return d->realTrack->name(); else return d->cachedName; } void MetaProxy::Track::setTitle( const QString &name ) { d->cachedName = name; } QString MetaProxy::Track::prettyName() const { if( d->realTrack ) return d->realTrack->prettyName(); else return Meta::Track::prettyName(); } QString MetaProxy::Track::sortableName() const { if( d->realTrack ) return d->realTrack->sortableName(); else return Meta::Track::sortableName(); } -KUrl +QUrl MetaProxy::Track::playableUrl() const { if( d->realTrack ) return d->realTrack->playableUrl(); else /* don't return d->url here, it may be something like * amarok-sqltrackuid://2f9277bb7e49962c1c4c5612811807a1 and Phonon may choke * on such urls trying to find a codec and causing hang (bug 308371) */ - return KUrl(); + return QUrl(); } QString MetaProxy::Track::prettyUrl() const { if( d->realTrack ) return d->realTrack->prettyUrl(); else - return d->url.prettyUrl(); + return d->url.toDisplayString(); } QString MetaProxy::Track::uidUrl() const { if( d->realTrack ) return d->realTrack->uidUrl(); else return d->url.url(); } QString MetaProxy::Track::notPlayableReason() const { if( !d->realTrack ) return i18n( "When Amarok was last closed, this track was at %1, but Amarok " "cannot find this track on the filesystem or in any of your collections " "anymore. You may try plugging in the device this track might be on.", prettyUrl() ); return d->realTrack->notPlayableReason(); } Meta::AlbumPtr MetaProxy::Track::album() const { return d->albumPtr; } void MetaProxy::Track::setAlbum( const QString &album ) { d->cachedAlbum = album; } void Track::setAlbumArtist( const QString &artist ) { Q_UNUSED( artist ); } Meta::ArtistPtr MetaProxy::Track::artist() const { return d->artistPtr; } void MetaProxy::Track::setArtist( const QString &artist ) { d->cachedArtist = artist; } Meta::GenrePtr MetaProxy::Track::genre() const { return d->genrePtr; } void MetaProxy::Track::setGenre( const QString &genre ) { d->cachedGenre = genre; } Meta::ComposerPtr MetaProxy::Track::composer() const { return d->composerPtr; } void MetaProxy::Track::setComposer( const QString &composer ) { d->cachedComposer = composer; } Meta::YearPtr MetaProxy::Track::year() const { return d->yearPtr; } void MetaProxy::Track::setYear( int year ) { d->cachedYear = year; } Meta::LabelList Track::labels() const { if( d->realTrack ) return d->realTrack->labels(); else return Meta::Track::labels(); } qreal MetaProxy::Track::bpm() const { if( d->realTrack ) return d->realTrack->bpm(); else return d->cachedBpm; } void MetaProxy::Track::setBpm( const qreal bpm ) { d->cachedBpm = bpm; } QString MetaProxy::Track::comment() const { if( d->realTrack ) return d->realTrack->comment(); else return QString(); // we don't cache comment } void Track::setComment( const QString & ) { // we don't cache comment } int MetaProxy::Track::trackNumber() const { if( d->realTrack ) return d->realTrack->trackNumber(); else return d->cachedTrackNumber; } void MetaProxy::Track::setTrackNumber( int number ) { d->cachedTrackNumber = number; } int MetaProxy::Track::discNumber() const { if( d->realTrack ) return d->realTrack->discNumber(); else return d->cachedDiscNumber; } void MetaProxy::Track::setDiscNumber( int discNumber ) { d->cachedDiscNumber = discNumber; } qint64 MetaProxy::Track::length() const { if( d->realTrack ) return d->realTrack->length(); else return d->cachedLength; } void MetaProxy::Track::setLength( qint64 length ) { d->cachedLength = length; } int MetaProxy::Track::filesize() const { if( d->realTrack ) return d->realTrack->filesize(); else return 0; } int MetaProxy::Track::sampleRate() const { if( d->realTrack ) return d->realTrack->sampleRate(); else return 0; } int MetaProxy::Track::bitrate() const { if( d->realTrack ) return d->realTrack->bitrate(); else return 0; } QDateTime MetaProxy::Track::createDate() const { if( d->realTrack ) return d->realTrack->createDate(); else return Meta::Track::createDate(); } QDateTime Track::modifyDate() const { if( d->realTrack ) return d->realTrack->modifyDate(); else return Meta::Track::modifyDate(); } qreal Track::replayGain( Meta::ReplayGainTag mode ) const { if( d->realTrack ) return d->realTrack->replayGain( mode ); else return Meta::Track::replayGain( mode ); } QString MetaProxy::Track::type() const { if( d->realTrack ) return d->realTrack->type(); else // just debugging, normal users shouldn't hit this return QString( "MetaProxy::Track" ); } void Track::prepareToPlay() { if( d->realTrack ) d->realTrack->prepareToPlay(); } void MetaProxy::Track::finishedPlaying( double playedFraction ) { if( d->realTrack ) d->realTrack->finishedPlaying( playedFraction ); } bool MetaProxy::Track::inCollection() const { if( d->realTrack ) return d->realTrack->inCollection(); else return false; } Collections::Collection * MetaProxy::Track::collection() const { if( d->realTrack ) return d->realTrack->collection(); else return 0; } QString Track::cachedLyrics() const { if( d->realTrack ) return d->realTrack->cachedLyrics(); else return Meta::Track::cachedLyrics(); } void Track::setCachedLyrics(const QString& lyrics) { if( d->realTrack ) d->realTrack->setCachedLyrics( lyrics ); else Meta::Track::setCachedLyrics( lyrics ); } void Track::addLabel( const QString &label ) { if( d->realTrack ) d->realTrack->addLabel( label ); else Meta::Track::addLabel( label ); } void Track::addLabel( const Meta::LabelPtr &label ) { if( d->realTrack ) d->realTrack->addLabel( label ); else Meta::Track::addLabel( label ); } void Track::removeLabel( const Meta::LabelPtr &label ) { if( d->realTrack ) d->realTrack->removeLabel( label ); else Meta::Track::removeLabel( label ); } void MetaProxy::Track::updateTrack( Meta::TrackPtr track ) { d->slotUpdateTrack( track ); } bool MetaProxy::Track::hasCapabilityInterface( Capabilities::Capability::Type type ) const { if( d->realTrack ) return d->realTrack->hasCapabilityInterface( type ); else return false; } Capabilities::Capability * MetaProxy::Track::createCapabilityInterface( Capabilities::Capability::Type type ) { if( d->realTrack ) return d->realTrack->createCapabilityInterface( type ); else return 0; } bool MetaProxy::Track::operator==( const Meta::Track &track ) const { const MetaProxy::Track *proxy = dynamic_cast( &track ); if( proxy && d->realTrack ) return d->realTrack == proxy->d->realTrack; else if( proxy ) return d->url == proxy->d->url; return d->realTrack && d->realTrack.data() == &track; } Meta::TrackEditorPtr Track::editor() { if( d->realTrack ) return d->realTrack->editor(); else return Meta::TrackEditorPtr( this ); } Meta::StatisticsPtr Track::statistics() { if( d->realTrack ) return d->realTrack->statistics(); else return Meta::Track::statistics(); } void Track::beginUpdate() { // nothing to do } void Track::endUpdate() { // we intentionally don't call metadataUpdated() so that thi first thing that // triggers metadataUpdated() is when the real track is found. } bool Track::isResolved() const { return d->realTrack; } diff --git a/src/core-impl/meta/proxy/MetaProxy.h b/src/core-impl/meta/proxy/MetaProxy.h index 3cf20fcd06..7ec872a497 100644 --- a/src/core-impl/meta/proxy/MetaProxy.h +++ b/src/core-impl/meta/proxy/MetaProxy.h @@ -1,164 +1,164 @@ /**************************************************************************************** * Copyright (c) 2007 Maximilian Kossick * * * * 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, see . * ****************************************************************************************/ #ifndef AMAROK_METAPROXY_H #define AMAROK_METAPROXY_H #include "amarok_export.h" #include "core/capabilities/Capability.h" #include "core/meta/Meta.h" #include "core/meta/TrackEditor.h" #include "core-impl/meta/proxy/MetaProxyWorker.h" #include namespace Collections { class TrackProvider; } namespace MetaProxy { class Track; typedef KSharedPtr TrackPtr; class AMAROK_EXPORT Track : public Meta::Track, public Meta::TrackEditor { public: class Private; enum LookupType { AutomaticLookup, ManualLookup }; /** * Construct a lazy-loading proxying track. You must assign this track to a * KSharedPtr right after constructing it. * * If @param lookupType is AutomaticLookup (the default), an asynchronous * job employing CollectionManager to lookup the track in TrackProviders is * enqueued and started right from this constructor. * * If @param lookupType is ManualLookup, lookup is not done automatically * and you are responsible to call lookupTrack() once it is feasible. This way * you can also optionally define which TrackProvider will be used. */ - Track( const KUrl &url, LookupType lookupType = AutomaticLookup ); + Track( const QUrl &url, LookupType lookupType = AutomaticLookup ); virtual ~Track(); /** * Tell MetaProxy::Track to start looking up the real track. Only valid if * this Track is constructed with lookupType = ManualLookup. This method * returns quickly and the lookup happens asynchronously in a thread (in * other words, @param provider, id supplied, must be thread-safe). * * If @param provider is null (the default), lookup happens in all * registered providers by employing CollectionManager. Otherwise lookup * only checks @param provider (still asynchronously). */ void lookupTrack( Collections::TrackProvider *provider = 0 ); // methods inherited from Meta::MetaCapability virtual bool hasCapabilityInterface( Capabilities::Capability::Type type ) const; virtual Capabilities::Capability* createCapabilityInterface( Capabilities::Capability::Type type ); // methods inherited from Meta::Base virtual QString name() const; virtual QString prettyName() const; virtual QString sortableName() const; // methods inherited from Meta::Track - virtual KUrl playableUrl() const; + virtual QUrl playableUrl() const; virtual QString prettyUrl() const; virtual QString uidUrl() const; virtual QString notPlayableReason() const; virtual Meta::AlbumPtr album() const; virtual Meta::ArtistPtr artist() const; virtual Meta::GenrePtr genre() const; virtual Meta::ComposerPtr composer() const; virtual Meta::YearPtr year() const; virtual Meta::LabelList labels() const; virtual qreal bpm() const; virtual QString comment() const; virtual qint64 length() const; virtual int filesize() const; virtual int sampleRate() const; virtual int bitrate() const; virtual QDateTime createDate() const; virtual QDateTime modifyDate() const; virtual int trackNumber() const; virtual int discNumber() const; virtual qreal replayGain( Meta::ReplayGainTag mode ) const; virtual QString type() const; virtual void prepareToPlay(); virtual void finishedPlaying( double playedFraction ); virtual bool inCollection() const; virtual Collections::Collection *collection() const; virtual QString cachedLyrics() const; virtual void setCachedLyrics( const QString &lyrics ); virtual void addLabel( const QString &label ); virtual void addLabel( const Meta::LabelPtr &label ); virtual void removeLabel( const Meta::LabelPtr &label ); virtual Meta::TrackEditorPtr editor(); virtual Meta::StatisticsPtr statistics(); virtual bool operator==( const Meta::Track &track ) const; // Meta::TrackEditor methods: virtual void setAlbum( const QString &album ); virtual void setAlbumArtist( const QString &artist ); virtual void setArtist( const QString &artist ); virtual void setComposer( const QString &composer ); virtual void setGenre( const QString &genre ); virtual void setYear( int year ); virtual void setComment( const QString &comment ); virtual void setTitle( const QString &name ); virtual void setTrackNumber( int number ); virtual void setDiscNumber( int discNumber ); virtual void setBpm( const qreal bpm ); virtual void beginUpdate(); virtual void endUpdate(); // custom MetaProxy methods /** * Return true if underlying track has already been found, false otherwise. */ bool isResolved() const; void setLength( qint64 length ); /** * MetaProxy will update the proxy with the track. */ void updateTrack( Meta::TrackPtr track ); private: Q_DISABLE_COPY( Track ) Private *const d; // constant pointer to non-constant object }; } #endif diff --git a/src/core-impl/meta/proxy/MetaProxyWorker.cpp b/src/core-impl/meta/proxy/MetaProxyWorker.cpp index de4422e027..0335e42fe3 100644 --- a/src/core-impl/meta/proxy/MetaProxyWorker.cpp +++ b/src/core-impl/meta/proxy/MetaProxyWorker.cpp @@ -1,93 +1,93 @@ /**************************************************************************************** * Copyright (c) 2012 Bart Cerneels * * * * 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, see . * ****************************************************************************************/ #include "MetaProxyWorker.h" #include "core/meta/Meta.h" #include "core-impl/collections/support/CollectionManager.h" using namespace MetaProxy; -Worker::Worker( const KUrl &url, Collections::TrackProvider *provider ) +Worker::Worker( const QUrl &url, Collections::TrackProvider *provider ) : m_url( url ) , m_provider( provider ) , m_stepsDoneReceived( 0 ) { connect( this, SIGNAL(done(ThreadWeaver::Job*)), SLOT(slotStepDone()) ); connect( this, SIGNAL(finishedLookup(Meta::TrackPtr)), SLOT(slotStepDone()) ); } void Worker::run() { Meta::TrackPtr track; if( m_provider ) { track = m_provider->trackForUrl( m_url ); emit finishedLookup( track ); return; } track = CollectionManager::instance()->trackForUrl( m_url ); if( track ) { emit finishedLookup( track ); return; } // no TrackProvider has a track for us yet, query new ones that are added. if( !track ) { connect( CollectionManager::instance(), SIGNAL(trackProviderAdded(Collections::TrackProvider*)), SLOT(slotNewTrackProvider(Collections::TrackProvider*)), Qt::DirectConnection ); // we may live in a thread w/out event loop connect( CollectionManager::instance(), SIGNAL(collectionAdded(Collections::Collection*)), SLOT(slotNewCollection(Collections::Collection*)), Qt::DirectConnection ); // we may live in a thread w/out event loop return; } } void Worker::slotNewTrackProvider( Collections::TrackProvider *newTrackProvider ) { if( !newTrackProvider ) return; if( newTrackProvider->possiblyContainsTrack( m_url ) ) { Meta::TrackPtr track = newTrackProvider->trackForUrl( m_url ); emit finishedLookup( track ); } } void Worker::slotNewCollection( Collections::Collection *newCollection ) { slotNewTrackProvider( newCollection ); } void Worker::slotStepDone() { m_stepsDoneReceived++; if( m_stepsDoneReceived >= 2 ) deleteLater(); } diff --git a/src/core-impl/meta/proxy/MetaProxyWorker.h b/src/core-impl/meta/proxy/MetaProxyWorker.h index fb19d65c38..e088ac81af 100644 --- a/src/core-impl/meta/proxy/MetaProxyWorker.h +++ b/src/core-impl/meta/proxy/MetaProxyWorker.h @@ -1,60 +1,60 @@ /**************************************************************************************** * Copyright (c) 2012 Bart Cerneels * * * * 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, see . * ****************************************************************************************/ #ifndef METAPROXY_METAPROXYWORKER_H #define METAPROXY_METAPROXYWORKER_H #include "core/collections/Collection.h" #include namespace MetaProxy { /** * Worker to get real track for MetaProxy::Track. Worker deletes itself somewhere * after emitting finishedLookup(). */ class Worker : public ThreadWeaver::Job { Q_OBJECT public: /** * If @param provider is null (the default), all providers registered to * CollectionManager are used and a watch for new providers is used. * Otherwise the lookup happes just in @param provider and is one-shot. */ - explicit Worker( const KUrl &url, Collections::TrackProvider *provider = 0 ); + explicit Worker( const QUrl &url, Collections::TrackProvider *provider = 0 ); //TrackForUrlWorker virtual methods virtual void run(); signals: void finishedLookup( Meta::TrackPtr track ); private slots: void slotNewTrackProvider( Collections::TrackProvider *newTrackProvider ); void slotNewCollection( Collections::Collection *newCollection ); void slotStepDone(); private: - KUrl m_url; + QUrl m_url; Collections::TrackProvider *m_provider; int m_stepsDoneReceived; }; } // namespace MetaProxy #endif // METAPROXY_METAPROXYWORKER_H diff --git a/src/core-impl/meta/proxy/MetaProxy_p.h b/src/core-impl/meta/proxy/MetaProxy_p.h index 7a0dd7b3a5..07a3d396df 100644 --- a/src/core-impl/meta/proxy/MetaProxy_p.h +++ b/src/core-impl/meta/proxy/MetaProxy_p.h @@ -1,456 +1,456 @@ /**************************************************************************************** * Copyright (c) 2007 Maximilian Kossick * * * * 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, see . * ****************************************************************************************/ #ifndef AMAROK_METAPROXY_P_H #define AMAROK_METAPROXY_P_H #include "core/collections/Collection.h" #include "core/meta/Meta.h" #include "core/meta/Observer.h" #include "core-impl/meta/stream/Stream.h" #include #include #include #include #include #include using namespace MetaProxy; class MetaProxy::Track::Private : public QObject, public Meta::Observer { Q_OBJECT public: Track *proxy; - KUrl url; + QUrl url; Meta::TrackPtr realTrack; QString cachedArtist; QString cachedAlbum; QString cachedName; QString cachedGenre; QString cachedComposer; int cachedYear; qint64 cachedLength; qreal cachedBpm; int cachedTrackNumber; int cachedDiscNumber; Meta::ArtistPtr artistPtr; Meta::AlbumPtr albumPtr; Meta::GenrePtr genrePtr; Meta::ComposerPtr composerPtr; Meta::YearPtr yearPtr; public: using Observer::metadataChanged; void metadataChanged( Meta::TrackPtr track ) { Q_UNUSED( track ) proxy->notifyObservers(); } public slots: void slotUpdateTrack( Meta::TrackPtr track ) { if( track ) { // special handling for streams that cannot fetch metadata until played, bug 305389 MetaStream::Track *stream = dynamic_cast( track.data() ); if( stream ) stream->setInitialInfo( cachedArtist, cachedAlbum, cachedName, cachedLength, cachedTrackNumber ); subscribeTo( track ); realTrack = track; // clear memory of now-unused cached fields: url.clear(); cachedArtist.clear(); cachedAlbum.clear(); cachedName.clear(); cachedGenre.clear(); cachedComposer.clear(); proxy->notifyObservers(); } } }; // internal helper classes class ProxyArtist : public Meta::Artist { public: ProxyArtist( MetaProxy::Track::Private *dptr ) : Meta::Artist() , d( dptr ) {} Meta::TrackList tracks() { Meta::TrackPtr realTrack = d ? d->realTrack : Meta::TrackPtr(); Meta::ArtistPtr artist = realTrack ? realTrack->artist() : Meta::ArtistPtr(); return artist ? artist->tracks() : Meta::TrackList(); } QString name() const { Meta::TrackPtr realTrack = d ? d->realTrack : Meta::TrackPtr(); if( realTrack ) { Meta::ArtistPtr artist = realTrack ? realTrack->artist() : Meta::ArtistPtr(); return artist ? artist->name() : QString(); } return d ? d->cachedArtist : QString(); } QString prettyName() const { Meta::TrackPtr realTrack = d ? d->realTrack : Meta::TrackPtr(); if( realTrack ) { Meta::ArtistPtr artist = realTrack ? realTrack->artist() : Meta::ArtistPtr(); return artist ? artist->prettyName() : QString(); } return d ? d->cachedArtist : QString(); } virtual bool operator==( const Meta::Artist &artist ) const { const ProxyArtist *proxy = dynamic_cast( &artist ); if( proxy ) { return d && proxy->d && d->realTrack && proxy->d->realTrack && d->realTrack->artist() && d->realTrack->artist() == proxy->d->realTrack->artist(); } else { return d && d->realTrack && d->realTrack->artist() && d->realTrack->artist().data() == &artist; } } MetaProxy::Track::Private * const d; }; /** TODO: what about MetaDataChanged? */ class ProxyAlbum : public Meta::Album { public: ProxyAlbum( MetaProxy::Track::Private *dptr ) : Meta::Album() , d( dptr ) {} bool hasCapabilityInterface( Capabilities::Capability::Type type ) const { if( d && d->realTrack && d->realTrack->album() ) return d->realTrack->album()->hasCapabilityInterface( type ); else return false; } Capabilities::Capability* createCapabilityInterface( Capabilities::Capability::Type type ) { if( d && d->realTrack && d->realTrack->album() ) return d->realTrack->album()->createCapabilityInterface( type ); else return 0; } bool isCompilation() const { if( d && d->realTrack && d->realTrack->album() ) return d->realTrack->album()->isCompilation(); else return false; } bool canUpdateCompilation() const { if( d && d->realTrack && d->realTrack->album() ) return d->realTrack->album()->canUpdateCompilation(); else return Meta::Album::canUpdateCompilation(); } void setCompilation( bool isCompilation ) { if( d && d->realTrack && d->realTrack->album() ) return d->realTrack->album()->setCompilation( isCompilation ); } bool hasAlbumArtist() const { if( d && d->realTrack && d->realTrack->album() ) return d->realTrack->album()->hasAlbumArtist(); else return false; } Meta::ArtistPtr albumArtist() const { if( d && d->realTrack && d->realTrack->album() ) return d->realTrack->album()->albumArtist(); else return Meta::ArtistPtr(); } Meta::TrackList tracks() { if( d && d->realTrack && d->realTrack->album() ) return d->realTrack->album()->tracks(); else return Meta::TrackList(); } QString name() const { if( d && d->realTrack ) { if ( d->realTrack->album() ) return d->realTrack->album()->name(); return QString(); } else if ( d ) return d->cachedAlbum; else return QString(); } QString prettyName() const { if( d && d->realTrack && d->realTrack->album() ) return d->realTrack->album()->prettyName(); else return name(); } QImage image( int size ) const { if( d && d->realTrack && d->realTrack->album() ) return d->realTrack->album()->image( size ); else return Meta::Album::image( size ); } bool hasImage( int size ) const { if( d && d->realTrack && d->realTrack->album() ) return d->realTrack->album()->hasImage( size ); else return Meta::Album::hasImage( size ); } - KUrl imageLocation( int size = 0 ) + QUrl imageLocation( int size = 0 ) { if( d && d->realTrack && d->realTrack->album() ) return d->realTrack->album()->imageLocation( size ); else return Meta::Album::imageLocation( size ); } bool canUpdateImage() const { if( d && d->realTrack && d->realTrack->album() ) return d->realTrack->album()->canUpdateImage(); else return Meta::Album::canUpdateImage(); } void setImage( const QImage &image ) { if( d && d->realTrack && d->realTrack->album() ) return d->realTrack->album()->setImage( image ); } void removeImage() { if( d && d->realTrack && d->realTrack->album() ) return d->realTrack->album()->removeImage(); } virtual bool operator==( const Meta::Album &album ) const { const ProxyAlbum *proxy = dynamic_cast( &album ); if( proxy ) { return d && proxy->d && d->realTrack && proxy->d->realTrack && d->realTrack->album() && ( *d->realTrack->album().data() ) == ( *proxy->d->realTrack->album().data() ); } else { return d && d->realTrack && d->realTrack->album() && ( *d->realTrack->album().data() ) == album; } } MetaProxy::Track::Private * const d; }; class ProxyGenre : public Meta::Genre { public: ProxyGenre( MetaProxy::Track::Private *dptr ) : Meta::Genre() , d( dptr ) {} QString name() const { if( d && d->realTrack && d->realTrack->genre() ) return d->realTrack->genre()->name(); else if( d ) return d->cachedGenre; else return QString(); } QString prettyName() const { if( d && d->realTrack && d->realTrack->genre() ) return d->realTrack->genre()->prettyName(); else return QString(); } Meta::TrackList tracks() { if( d && d->realTrack && d->realTrack->genre() ) return d->realTrack->genre()->tracks(); else return Meta::TrackList(); } virtual bool operator==( const Meta::Genre &genre ) const { const ProxyGenre *proxy = dynamic_cast( &genre ); if( proxy ) { return d && proxy->d && d->realTrack && proxy->d->realTrack && d->realTrack->genre() && d->realTrack->genre() == proxy->d->realTrack->genre(); } else { return d && d->realTrack && d->realTrack->genre() && d->realTrack->genre().data() == &genre; } } MetaProxy::Track::Private * const d; }; class ProxyComposer : public Meta::Composer { public: ProxyComposer( MetaProxy::Track::Private *dptr ) : Meta::Composer() , d( dptr ) {} QString name() const { if( d && d->realTrack && d->realTrack->composer() ) return d->realTrack->composer()->name(); else if ( d ) return d->cachedComposer; else return QString(); } QString prettyName() const { if( d && d->realTrack && d->realTrack->composer()) return d->realTrack->composer()->prettyName(); else return name(); } Meta::TrackList tracks() { if( d && d->realTrack && d->realTrack->composer() ) return d->realTrack->composer()->tracks(); else return Meta::TrackList(); } virtual bool operator==( const Meta::Composer &composer ) const { const ProxyComposer *proxy = dynamic_cast( &composer ); if( proxy ) { return d && proxy->d && d->realTrack && proxy->d->realTrack && d->realTrack->composer() && d->realTrack->composer() == proxy->d->realTrack->composer(); } else { return d && d->realTrack && d->realTrack->composer() && d->realTrack->composer().data() == &composer; } } MetaProxy::Track::Private * const d; }; class ProxyYear : public Meta::Year { public: ProxyYear( MetaProxy::Track::Private *dptr ) : Meta::Year() , d( dptr ) {} QString name() const { if( d && d->realTrack && d->realTrack->year() ) return d->realTrack->year()->name(); else if( d ) return QString::number(d->cachedYear); else return QString(); } QString prettyName() const { if( d && d->realTrack && d->realTrack->year() ) return d->realTrack->year()->prettyName(); else return name(); } Meta::TrackList tracks() { if( d && d->realTrack && d->realTrack->year() ) return d->realTrack->year()->tracks(); else return Meta::TrackList(); } virtual bool operator==( const Meta::Year &year ) const { const ProxyYear *proxy = dynamic_cast( &year ); if( proxy ) { return d && proxy->d && d->realTrack && proxy->d->realTrack && d->realTrack->year() && d->realTrack->year() == proxy->d->realTrack->year(); } else { return d && d->realTrack && d->realTrack->year() && d->realTrack->year().data() == &year; } } MetaProxy::Track::Private * const d; }; #endif diff --git a/src/core-impl/meta/stream/Stream.cpp b/src/core-impl/meta/stream/Stream.cpp index 05c48cedde..a8def71c40 100644 --- a/src/core-impl/meta/stream/Stream.cpp +++ b/src/core-impl/meta/stream/Stream.cpp @@ -1,193 +1,193 @@ /**************************************************************************************** * Copyright (c) 2007-2008 Maximilian Kossick * * * * 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, see . * ****************************************************************************************/ #include "core-impl/meta/stream/Stream.h" #include "core-impl/meta/stream/Stream_p.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include "core/meta/Meta.h" #include "core-impl/meta/default/DefaultMetaTypes.h" #include "core-impl/support/UrlStatisticsStore.h" #include #include using namespace MetaStream; -Track::Track( const KUrl &url ) +Track::Track( const QUrl &url ) : Meta::Track() , d( new Track::Private( this ) ) { d->url = url; d->artistPtr = Meta::ArtistPtr( new StreamArtist( d ) ); d->albumPtr = Meta::AlbumPtr( new StreamAlbum( d ) ); d->genrePtr = Meta::GenrePtr( new StreamGenre( d ) ); d->composerPtr = Meta::ComposerPtr( new Meta::DefaultComposer() ); d->yearPtr = Meta::YearPtr( new Meta::DefaultYear() ); } Track::~Track() { delete d; } QString Track::name() const { if( d->title.isEmpty() ) return i18n( "Stream (%1)", d->url.url() ); return d->title; } -KUrl +QUrl Track::playableUrl() const { return d->url; } QString Track::prettyUrl() const { return playableUrl().prettyUrl(); } QString Track::uidUrl() const { return playableUrl().url(); } QString Track::notPlayableReason() const { return networkNotPlayableReason(); } Meta::AlbumPtr Track::album() const { return d->albumPtr; } Meta::ArtistPtr Track::artist() const { return d->artistPtr; } Meta::GenrePtr Track::genre() const { return d->genrePtr; } Meta::ComposerPtr Track::composer() const { return d->composerPtr; } Meta::YearPtr Track::year() const { return d->yearPtr; } qreal Track::bpm() const { return -1.0; } QString Track::comment() const { return d->comment; } int Track::trackNumber() const { return d->trackNumber; } int Track::discNumber() const { return 0; } qint64 Track::length() const { return d->length; } int Track::filesize() const { return 0; } int Track::sampleRate() const { return 0; } int Track::bitrate() const { return 0; } void Track::finishedPlaying( double playedFraction ) { // playedFraction will nearly always be 1, because EngineController updates length // just before calling finishedPlaying(). Mimic Last.fm scrobbling wrt min length // requirement, tracks shorter than 30s are often ads etc. if( length() < 30 * 1000 ) return; Meta::Track::finishedPlaying( playedFraction ); } QString Track::type() const { // don't localize. See EngineController quirks return "stream"; } void Track::setInitialInfo( const QString &artist, const QString &album, const QString &title, qint64 length, int trackNumber ) { if( d->artist.isEmpty() ) d->artist = artist; if( d->album.isEmpty() ) d->album = album; if( d->title.isEmpty() ) d->title = title; if( d->length == 0 ) d->length = length; if( d->trackNumber == 0 ) d->trackNumber = trackNumber; } #include "Stream_p.moc" diff --git a/src/core-impl/meta/stream/Stream.h b/src/core-impl/meta/stream/Stream.h index 42fc30f8e9..aeca36e50b 100644 --- a/src/core-impl/meta/stream/Stream.h +++ b/src/core-impl/meta/stream/Stream.h @@ -1,80 +1,80 @@ /**************************************************************************************** * Copyright (c) 2007 Maximilian Kossick * * * * 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, see . * ****************************************************************************************/ #ifndef AMAROK_STREAM_H #define AMAROK_STREAM_H #include "amarok_export.h" #include "core/meta/Meta.h" namespace MetaStream { class AMAROK_EXPORT Track : public Meta::Track { public: class Private; - Track( const KUrl &url ); + Track( const QUrl &url ); virtual ~Track(); // methods inherited from Meta::Base virtual QString name() const; // methods inherited from Meta::Track - virtual KUrl playableUrl() const; + virtual QUrl playableUrl() const; virtual QString prettyUrl() const; virtual QString uidUrl() const; virtual QString notPlayableReason() const; virtual Meta::AlbumPtr album() const; virtual Meta::ArtistPtr artist() const; virtual Meta::GenrePtr genre() const; virtual Meta::ComposerPtr composer() const; virtual Meta::YearPtr year() const; virtual qreal bpm() const; virtual QString comment() const; virtual int trackNumber() const; virtual int discNumber() const; virtual qint64 length() const; virtual int filesize() const; virtual int sampleRate() const; virtual int bitrate() const; virtual void finishedPlaying( double playedFraction ); virtual QString type() const; // MetaStream::Track methods, used to restore initial stream info /** * Set initial values to display before more accurate info can be fetched. * This method doesn't call notifyObservers(), it is the caller's * responsibility; it also doesn't overwrite already filled entries. * * @param length is in milliseconds */ void setInitialInfo( const QString &artist, const QString &album, const QString &title, qint64 length, int trackNumber ); private: Private * const d; }; } #endif diff --git a/src/core-impl/meta/stream/Stream_p.h b/src/core-impl/meta/stream/Stream_p.h index a4378f86bd..1720b74297 100644 --- a/src/core-impl/meta/stream/Stream_p.h +++ b/src/core-impl/meta/stream/Stream_p.h @@ -1,202 +1,202 @@ /**************************************************************************************** * Copyright (c) 2007-2008 Maximilian Kossick * * Copyright (c) 2008 Mark Kretschmann * * * * 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, see . * ****************************************************************************************/ #ifndef AMAROK_STREAM_P_H #define AMAROK_STREAM_P_H #include "EngineController.h" #include "core/meta/Meta.h" #include "core/meta/support/MetaConstants.h" #include "core/support/Debug.h" #include "core-impl/meta/default/DefaultMetaTypes.h" #include "covermanager/CoverCache.h" #include using namespace MetaStream; class MetaStream::Track::Private : public QObject { Q_OBJECT public: Private( Track *t ) : trackNumber( 0 ) , length( 0 ) , track( t ) { EngineController *engine = The::engineController(); if( !engine ) return; // engine might not be available during tests, silence the warning // force a direct connection or slot might not be called because of thread // affinity. (see BUG 300334) connect( engine, SIGNAL(currentMetadataChanged( QVariantMap) ), this, SLOT(currentMetadataChanged( QVariantMap )), Qt::DirectConnection ); } public Q_SLOTS: void currentMetadataChanged( QVariantMap metaData ) { const QUrl metaDataUrl = metaData.value( Meta::Field::URL ).toUrl(); if( metaDataUrl == url ) { // keep synchronized to EngineController::slotMetaDataChanged() if( metaData.contains( Meta::Field::ARTIST ) ) artist = metaData.value( Meta::Field::ARTIST ).toString(); if( metaData.contains( Meta::Field::TITLE ) ) title = metaData.value( Meta::Field::TITLE ).toString(); if( metaData.contains( Meta::Field::ALBUM ) ) album = metaData.value( Meta::Field::ALBUM ).toString(); if( metaData.contains( Meta::Field::GENRE ) ) genre = metaData.value( Meta::Field::GENRE ).toString(); if( metaData.contains( Meta::Field::TRACKNUMBER ) ) trackNumber = metaData.value( Meta::Field::TRACKNUMBER ).toInt(); if( metaData.contains( Meta::Field::COMMENT ) ) comment = metaData.value( Meta::Field::COMMENT ).toString(); if( metaData.contains( Meta::Field::LENGTH ) ) length = metaData.value( Meta::Field::LENGTH ).value(); //TODO: move special handling to subclass or using some configurable XSPF // Special demangling of artist/title for Shoutcast streams, which usually // have "Artist - Title" in the title tag: if( artist.isEmpty() && title.contains( " - " ) ) { const QStringList artist_title = title.split( " - " ); if( artist_title.size() >= 2 ) { artist = artist_title[0]; title = title.remove( 0, artist.length() + 3 ); } } track->notifyObservers(); } } public: - KUrl url; + QUrl url; QString title; QString artist; QString album; QString genre; int trackNumber; QString comment; qint64 length; Meta::ArtistPtr artistPtr; Meta::AlbumPtr albumPtr; Meta::GenrePtr genrePtr; Meta::ComposerPtr composerPtr; Meta::YearPtr yearPtr; private: Track *track; }; // internal helper classes class StreamArtist : public Meta::DefaultArtist { public: StreamArtist( MetaStream::Track::Private *dptr ) : DefaultArtist() , d( dptr ) {} QString name() const { if( d && !d->artist.isEmpty() ) return d->artist; return DefaultArtist::name(); } MetaStream::Track::Private * const d; }; class StreamAlbum : public Meta::DefaultAlbum { public: StreamAlbum( MetaStream::Track::Private *dptr ) : DefaultAlbum() , d( dptr ) {} ~StreamAlbum() { CoverCache::invalidateAlbum( this ); } bool hasAlbumArtist() const { return false; } QString name() const { if( d && !d->album.isEmpty() ) return d->album; return DefaultAlbum::name(); } bool hasImage( int size ) const { if( m_cover.isNull() ) return Meta::Album::hasImage( size ); else return true; } QImage image( int size ) const { if( m_cover.isNull() ) return Meta::Album::image( size ); else return m_cover.scaled( size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation ); } void setImage( const QImage &image ) { m_cover = image; CoverCache::invalidateAlbum( this ); } MetaStream::Track::Private * const d; QImage m_cover; }; class StreamGenre : public Meta::DefaultGenre { public: StreamGenre( MetaStream::Track::Private *dptr ) : DefaultGenre() , d( dptr ) {} QString name() const { if( d && !d->genre.isEmpty() ) return d->genre; return DefaultGenre::name(); } MetaStream::Track::Private * const d; }; #endif diff --git a/src/core-impl/meta/timecode/TimecodeMeta.cpp b/src/core-impl/meta/timecode/TimecodeMeta.cpp index 81adcd245d..059a727291 100644 --- a/src/core-impl/meta/timecode/TimecodeMeta.cpp +++ b/src/core-impl/meta/timecode/TimecodeMeta.cpp @@ -1,620 +1,620 @@ /**************************************************************************************** * Copyright (c) 2009 Nikolaj Hald Nielsen * * * * 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, see . * ****************************************************************************************/ #include "core-impl/meta/timecode/TimecodeMeta.h" #include "core/support/Debug.h" #include "covermanager/CoverCache.h" #include "covermanager/CoverFetchingActions.h" #include "covermanager/CoverFetcher.h" #include "core/capabilities/ActionsCapability.h" #include "core/capabilities/Capability.h" #include "core/capabilities/BoundedPlaybackCapability.h" #include "core-impl/capabilities/AlbumActionsCapability.h" #include "core-impl/capabilities/timecode/TimecodeBoundedPlaybackCapability.h" using namespace Meta; using namespace Capabilities; ////////////////// TRACK ////////////////// TimecodeTrack::TimecodeTrack( const QString &name, const QString &url, qint64 start, qint64 end ) : m_name( name ) , m_start( start ) , m_end( end ) , m_length( end - start ) , m_bpm( -1.0 ) , m_trackNumber( 0 ) , m_discNumber( 0 ) , m_comment( QString() ) , m_playableUrl( url ) , m_updatedFields( 0 ) { m_displayUrl = url + ':' + QString::number( start ) + '-' + QString::number( end ); } TimecodeTrack::~ TimecodeTrack() { } QString TimecodeTrack::name() const { return m_name; } -KUrl +QUrl TimecodeTrack::playableUrl() const { return m_playableUrl; } QString TimecodeTrack::uidUrl() const { return m_displayUrl; } QString TimecodeTrack::prettyUrl() const { return m_displayUrl; } QString TimecodeTrack::notPlayableReason() const { return localFileNotPlayableReason( m_playableUrl ); } AlbumPtr TimecodeTrack::album() const { return AlbumPtr::staticCast( m_album ); } ArtistPtr TimecodeTrack::artist() const { return ArtistPtr::staticCast( m_artist ); } GenrePtr TimecodeTrack::genre() const { return GenrePtr::staticCast( m_genre ); } ComposerPtr TimecodeTrack::composer() const { return ComposerPtr::staticCast( m_composer ); } YearPtr TimecodeTrack::year() const { return YearPtr::staticCast( m_year ); } qreal TimecodeTrack::bpm() const { return m_bpm; } QString TimecodeTrack::comment() const { return m_comment; } int TimecodeTrack::bitrate() const { return -1; } int TimecodeTrack::sampleRate() const { return -1; } int TimecodeTrack::filesize() const { return -1; } qint64 TimecodeTrack::length() const { return m_length; } int TimecodeTrack::trackNumber() const { return m_trackNumber; } int TimecodeTrack::discNumber() const { return m_discNumber; } QString TimecodeTrack::type() const { return QString(); } void TimecodeTrack::setAlbum( const QString &newAlbum ) { m_updatedFields |= ALBUM_UPDATED; m_fields.insert( ALBUM_UPDATED, newAlbum ); } void TimecodeTrack::setArtist( const QString &newArtist ) { m_updatedFields |= ARTIST_UPDATED; m_fields.insert( ARTIST_UPDATED, newArtist ); } void TimecodeTrack::setComposer( const QString &newComposer ) { m_updatedFields |= COMPOSER_UPDATED; m_fields.insert( COMPOSER_UPDATED, newComposer ); } void TimecodeTrack::setGenre( const QString &newGenre ) { m_updatedFields |= GENRE_UPDATED; m_fields.insert( GENRE_UPDATED, newGenre ); } void TimecodeTrack::setYear( int newYear ) { m_updatedFields |= YEAR_UPDATED; m_fields.insert( YEAR_UPDATED, QString::number( newYear ) ); } void TimecodeTrack::setBpm( const qreal newBpm ) { m_updatedFields |= BPM_UPDATED; m_fields.insert( BPM_UPDATED, QString::number( (qreal) newBpm ) ); } void TimecodeTrack::setTitle( const QString &newTitle ) { m_updatedFields |= TITLE_UPDATED; m_fields.insert( TITLE_UPDATED, newTitle ); } void TimecodeTrack::setComment( const QString &newComment ) { m_updatedFields |= COMMENT_UPDATED; m_fields.insert( COMMENT_UPDATED, newComment ); } void TimecodeTrack::setTrackNumber( int newTrackNumber ) { m_updatedFields |= TRACKNUMBER_UPDATED; m_fields.insert( TRACKNUMBER_UPDATED, QString::number( newTrackNumber ) ); } void TimecodeTrack::setDiscNumber( int newDiscNumber ) { m_updatedFields |= DISCNUMBER_UPDATED; m_fields.insert( DISCNUMBER_UPDATED, QString::number( newDiscNumber ) ); } void TimecodeTrack::beginUpdate() { m_updatedFields = 0; m_fields.clear(); } void TimecodeTrack::endUpdate() { bool updateCover = false; if ( m_updatedFields & ALBUM_UPDATED ) { //create a new album: m_album = TimecodeAlbumPtr( new TimecodeAlbum( m_fields.value( ALBUM_UPDATED ) ) ); m_album->addTrack( TimecodeTrackPtr( this ) ); setAlbum( m_album ); m_album->setAlbumArtist( m_artist ); } if ( m_updatedFields & ARTIST_UPDATED ) { //create a new album: m_artist = TimecodeArtistPtr( new TimecodeArtist( m_fields.value( ARTIST_UPDATED ) ) ); m_artist->addTrack( TimecodeTrackPtr( this ) ); setArtist( m_artist ); m_album->setAlbumArtist( m_artist ); updateCover = true; } if ( m_updatedFields & COMPOSER_UPDATED ) { //create a new album: m_composer = TimecodeComposerPtr( new TimecodeComposer( m_fields.value( COMPOSER_UPDATED ) ) ); m_composer->addTrack( TimecodeTrackPtr( this ) ); setComposer( m_composer ); } if ( m_updatedFields & GENRE_UPDATED ) { //create a new album: m_genre = TimecodeGenrePtr( new TimecodeGenre( m_fields.value( GENRE_UPDATED ) ) ); m_genre->addTrack( TimecodeTrackPtr( this ) ); setGenre( m_genre ); } if ( m_updatedFields & YEAR_UPDATED ) { //create a new album: m_year = TimecodeYearPtr( new TimecodeYear( m_fields.value( YEAR_UPDATED ) ) ); m_year->addTrack( TimecodeTrackPtr( this ) ); setYear( m_year ); } if ( m_updatedFields & BPM_UPDATED ) { m_bpm = m_fields.value( BPM_UPDATED ).toDouble(); } if ( m_updatedFields & TITLE_UPDATED ) { //create a new album: m_name = m_fields.value( TITLE_UPDATED ); updateCover = true; } if ( m_updatedFields & COMMENT_UPDATED ) { //create a new album: m_comment = m_fields.value( COMMENT_UPDATED ); } if ( m_updatedFields & TRACKNUMBER_UPDATED ) { //create a new album: m_trackNumber = m_fields.value( TRACKNUMBER_UPDATED ).toInt(); } if ( m_updatedFields & DISCNUMBER_UPDATED ) { //create a new album: m_discNumber = m_fields.value( DISCNUMBER_UPDATED ).toInt(); } if ( updateCover ) The::coverFetcher()->queueAlbum( AlbumPtr::staticCast( m_album ) ); m_updatedFields = 0; m_fields.clear(); notifyObservers(); } void TimecodeTrack::setAlbum( TimecodeAlbumPtr album ) { m_album = album; } void TimecodeTrack::setAlbumArtist( const QString & ) { // no suport for it } void TimecodeTrack::setYear( TimecodeYearPtr year ) { m_year = year; } void TimecodeTrack::setGenre( TimecodeGenrePtr genre ) { m_genre = genre; } void TimecodeTrack::setComposer( TimecodeComposerPtr composer ) { m_composer = composer; } void TimecodeTrack::setArtist( TimecodeArtistPtr artist ) { m_artist = artist; } bool TimecodeTrack::hasCapabilityInterface( Capabilities::Capability::Type type ) const { return type == Capabilities::Capability::BoundedPlayback; } Capabilities::Capability * TimecodeTrack::createCapabilityInterface( Capabilities::Capability::Type type ) { DEBUG_BLOCK if ( type == Capabilities::Capability::BoundedPlayback ) return new Capabilities::TimecodeBoundedPlaybackCapability( this ); else return 0; } TrackEditorPtr TimecodeTrack::editor() { return TrackEditorPtr( this ); } qint64 Meta::TimecodeTrack::start() { return m_start; } qint64 Meta::TimecodeTrack::end() { return m_end; } ////////////////// ARTIST ////////////////// TimecodeArtist::TimecodeArtist( const QString & name ) : m_name( name ) { } TimecodeArtist::~ TimecodeArtist() { } QString TimecodeArtist::name() const { return m_name; } TrackList TimecodeArtist::tracks() { return m_tracks; } AlbumList TimecodeArtist::albums() { return AlbumList(); } void TimecodeArtist::addTrack( TimecodeTrackPtr track ) { m_tracks.append( TrackPtr::staticCast( track ) ); } ////////////////// ALBUM ////////////////// TimecodeAlbum::TimecodeAlbum( const QString & name ) : QObject() , m_name( name ) , m_isCompilation( false ) { } TimecodeAlbum::~ TimecodeAlbum() { CoverCache::invalidateAlbum( this ); } QString TimecodeAlbum::name() const { return m_name; } bool TimecodeAlbum::isCompilation() const { return m_isCompilation; } bool TimecodeAlbum::hasAlbumArtist() const { return !m_albumArtist.isNull(); } ArtistPtr TimecodeAlbum::albumArtist() const { return ArtistPtr::staticCast( m_albumArtist ); } TrackList TimecodeAlbum::tracks() { return m_tracks; } QImage TimecodeAlbum::image( int size ) const { if( m_cover.isNull() ) return Meta::Album::image( size ); else return m_cover.scaled( size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation ); } bool TimecodeAlbum::canUpdateImage() const { return true; } void TimecodeAlbum::setImage( const QImage &image ) { m_cover = image; CoverCache::invalidateAlbum( this ); notifyObservers(); } void TimecodeAlbum::addTrack( TimecodeTrackPtr track ) { m_tracks.append( TrackPtr::staticCast( track ) ); } void TimecodeAlbum::setAlbumArtist( TimecodeArtistPtr artist ) { m_albumArtist = artist; } bool TimecodeAlbum::hasCapabilityInterface( Capabilities::Capability::Type type ) const { switch( type ) { case Capabilities::Capability::Actions: return true; default: return false; } } Capabilities::Capability* TimecodeAlbum::createCapabilityInterface( Capabilities::Capability::Type type ) { switch( type ) { case Capabilities::Capability::Actions: return new AlbumActionsCapability( Meta::AlbumPtr( this ) ); default: return 0; } } ////////////////// GENRE ////////////////// TimecodeGenre::TimecodeGenre(const QString & name) : m_name( name ) { } TimecodeGenre::~ TimecodeGenre() { } QString TimecodeGenre::name() const { return m_name; } TrackList TimecodeGenre::tracks() { return tracks(); } void TimecodeGenre::addTrack( TimecodeTrackPtr track ) { m_tracks.append( TrackPtr::staticCast( track ) ); } ////////////////// COMPOSER ////////////////// TimecodeComposer::TimecodeComposer( const QString & name ) : m_name( name ) { } TimecodeComposer::~ TimecodeComposer() { } QString TimecodeComposer::name() const { return m_name; } TrackList TimecodeComposer::tracks() { return m_tracks; } void TimecodeComposer::addTrack( TimecodeTrackPtr track ) { m_tracks.append( TrackPtr::staticCast( track ) ); } ////////////////// YEAR ////////////////// TimecodeYear::TimecodeYear( const QString & name ) : m_name( name ) { } TimecodeYear::~ TimecodeYear() { } QString TimecodeYear::name() const { return m_name; } TrackList TimecodeYear::tracks() { return m_tracks; } void TimecodeYear::addTrack( TimecodeTrackPtr track ) { m_tracks.append( TrackPtr::staticCast( track ) ); } diff --git a/src/core-impl/meta/timecode/TimecodeMeta.h b/src/core-impl/meta/timecode/TimecodeMeta.h index 8227126f93..7f8d635ab5 100644 --- a/src/core-impl/meta/timecode/TimecodeMeta.h +++ b/src/core-impl/meta/timecode/TimecodeMeta.h @@ -1,276 +1,276 @@ /**************************************************************************************** * Copyright (c) 2009 Nikolaj Hald Nielsen * * * * 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, see . * ****************************************************************************************/ #ifndef TIMECODEMETA_H #define TIMECODEMETA_H #include "core/meta/Meta.h" #include "core/meta/TrackEditor.h" class QAction; namespace Meta { class TimecodeTrack; class TimecodeAlbum; class TimecodeArtist; class TimecodeGenre; class TimecodeComposer; class TimecodeYear; typedef KSharedPtr TimecodeTrackPtr; typedef KSharedPtr TimecodeArtistPtr; typedef KSharedPtr TimecodeAlbumPtr; typedef KSharedPtr TimecodeGenrePtr; typedef KSharedPtr TimecodeComposerPtr; typedef KSharedPtr TimecodeYearPtr; class TimecodeTrack : public Track, public TrackEditor { public: TimecodeTrack( const QString &name, const QString &url, qint64 start, qint64 end ); virtual ~TimecodeTrack(); virtual QString name() const; - virtual KUrl playableUrl() const; + virtual QUrl playableUrl() const; virtual QString uidUrl() const; virtual QString prettyUrl() const; virtual QString notPlayableReason() const; virtual AlbumPtr album() const; virtual ArtistPtr artist() const; virtual GenrePtr genre() const; virtual ComposerPtr composer() const; virtual YearPtr year() const; virtual qreal bpm() const; virtual QString comment() const; virtual qint64 length() const; virtual int filesize() const; virtual int sampleRate() const; virtual int bitrate() const; virtual int trackNumber() const; virtual int discNumber() const; virtual QString type() const; virtual bool hasCapabilityInterface( Capabilities::Capability::Type type ) const; virtual Capabilities::Capability *createCapabilityInterface( Capabilities::Capability::Type type ); virtual TrackEditorPtr editor(); // TrackEditor methods virtual void setAlbum( const QString &newAlbum ); virtual void setAlbumArtist( const QString &newAlbumArtist ); virtual void setArtist( const QString &newArtist ); virtual void setComposer( const QString &newComposer ); virtual void setGenre( const QString &newGenre ); virtual void setYear( int newYear ); virtual void setTitle( const QString &newTitle ); virtual void setComment( const QString &newComment ); virtual void setTrackNumber( int newTrackNumber ); virtual void setDiscNumber( int newDiscNumber ); virtual void setBpm( const qreal newBpm ); virtual void beginUpdate(); virtual void endUpdate(); //TimecodeTrack specific methods void setAlbum( TimecodeAlbumPtr album ); void setArtist( TimecodeArtistPtr artist ); void setComposer( TimecodeComposerPtr composer ); void setGenre( TimecodeGenrePtr genre ); void setYear( TimecodeYearPtr year ); qint64 start(); qint64 end(); private: //TimecodeCollection *m_collection; TimecodeArtistPtr m_artist; TimecodeAlbumPtr m_album; TimecodeGenrePtr m_genre; TimecodeComposerPtr m_composer; TimecodeYearPtr m_year; QString m_name; QString m_type; qint64 m_start; qint64 m_end; qint64 m_length; qreal m_bpm; int m_trackNumber; int m_discNumber; QString m_comment; QString m_displayUrl; QString m_playableUrl; int m_updatedFields; QMap m_fields; enum { ALBUM_UPDATED = 1 << 0, ARTIST_UPDATED = 1 << 1, COMPOSER_UPDATED = 1 << 2, GENRE_UPDATED = 1 << 3, YEAR_UPDATED = 1 << 4, TITLE_UPDATED = 1 << 5, COMMENT_UPDATED = 1 << 6, TRACKNUMBER_UPDATED = 1 << 7, DISCNUMBER_UPDATED = 1 << 8, BPM_UPDATED = 1 << 9 }; }; class TimecodeArtist : public Meta::Artist { public: TimecodeArtist( const QString &name ); virtual ~TimecodeArtist(); virtual QString name() const; virtual TrackList tracks(); virtual AlbumList albums(); bool operator==( const Meta::Artist &other ) const { return name() == other.name(); } //TimecodeArtist specific methods void addTrack( TimecodeTrackPtr track ); private: QString m_name; TrackList m_tracks; }; class TimecodeAlbum : public QObject, public Meta::Album { Q_OBJECT public: TimecodeAlbum( const QString &name ); virtual ~TimecodeAlbum(); virtual QString name() const; virtual bool isCompilation() const; virtual bool hasAlbumArtist() const; virtual ArtistPtr albumArtist() const; virtual TrackList tracks(); virtual QImage image( int size = 0 ) const; virtual bool canUpdateImage() const; virtual void setImage( const QImage &image ); virtual bool hasCapabilityInterface( Capabilities::Capability::Type type ) const; virtual Capabilities::Capability* createCapabilityInterface( Capabilities::Capability::Type type ); //TimecodeAlbum specific methods void addTrack( TimecodeTrackPtr track ); void setAlbumArtist( TimecodeArtistPtr artist ); bool operator==( const Meta::Album &other ) const { return name() == other.name(); } private: QString m_name; TrackList m_tracks; bool m_isCompilation; TimecodeArtistPtr m_albumArtist; QImage m_cover; }; class TimecodeGenre : public Meta::Genre { public: TimecodeGenre( const QString &name ); virtual ~TimecodeGenre(); virtual QString name() const; virtual TrackList tracks(); bool operator==( const Meta::Genre &other ) const { return name() == other.name(); } //TimecodeGenre specific methods void addTrack( TimecodeTrackPtr track ); private: QString m_name; TrackList m_tracks; }; class TimecodeComposer : public Meta::Composer { public: TimecodeComposer( const QString &name ); virtual ~TimecodeComposer(); virtual QString name() const; virtual TrackList tracks(); bool operator==( const Meta::Composer &other ) const { return name() == other.name(); } //TimecodeComposer specific methods void addTrack( TimecodeTrackPtr track ); private: QString m_name; TrackList m_tracks; }; class TimecodeYear : public Meta::Year { public: TimecodeYear( const QString &name ); virtual ~TimecodeYear(); virtual QString name() const; virtual TrackList tracks(); bool operator==( const Meta::Year &other ) const { return name() == other.name(); } //TimecodeYear specific methods void addTrack( TimecodeTrackPtr track ); private: QString m_name; TrackList m_tracks; }; } #endif diff --git a/src/core-impl/meta/timecode/TimecodeTrackProvider.cpp b/src/core-impl/meta/timecode/TimecodeTrackProvider.cpp index 12b6b6059b..2c6fd52c28 100644 --- a/src/core-impl/meta/timecode/TimecodeTrackProvider.cpp +++ b/src/core-impl/meta/timecode/TimecodeTrackProvider.cpp @@ -1,55 +1,55 @@ /**************************************************************************************** * Copyright (c) 2009 Nikolaj Hald Nielsen * * * * 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, see . * ****************************************************************************************/ #include "TimecodeTrackProvider.h" #include "TimecodeMeta.h" #include "QRegExp" TimecodeTrackProvider::TimecodeTrackProvider() { } TimecodeTrackProvider::~TimecodeTrackProvider() { } -bool TimecodeTrackProvider::possiblyContainsTrack( const KUrl & url ) const +bool TimecodeTrackProvider::possiblyContainsTrack( const QUrl &url ) const { return url.url().contains( QRegExp(":\\d+-\\d+$") ); } -Meta::TrackPtr TimecodeTrackProvider::trackForUrl( const KUrl & url ) +Meta::TrackPtr TimecodeTrackProvider::trackForUrl( const QUrl &url ) { QString urlString = url.url(); QRegExp rx; rx.setPattern( "^(.+):(\\d+)-(\\d+)$" ); if( rx.indexIn( urlString ) != -1 ) { QString baseUrl = rx.cap(1); int start = rx.cap(2).toInt(); int end = rx.cap(3).toInt(); Meta::TimecodeTrack * track = new Meta::TimecodeTrack( "TimecodeTrack", baseUrl, start, end ); return Meta::TrackPtr( track ); } return Meta::TrackPtr(); } diff --git a/src/core-impl/meta/timecode/TimecodeTrackProvider.h b/src/core-impl/meta/timecode/TimecodeTrackProvider.h index ce0701ec89..2decab9e39 100644 --- a/src/core-impl/meta/timecode/TimecodeTrackProvider.h +++ b/src/core-impl/meta/timecode/TimecodeTrackProvider.h @@ -1,38 +1,38 @@ /**************************************************************************************** * Copyright (c) 2009 Nikolaj Hald Nielsen * * * * 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, see . * ****************************************************************************************/ #ifndef TIMECODETRACKPROVIDER_H #define TIMECODETRACKPROVIDER_H #include "core/collections/Collection.h" /** A track provider that recognizes timecode track urls @author Nikolaj Hald Nielsen */ class TimecodeTrackProvider : public Collections::TrackProvider{ public: TimecodeTrackProvider(); ~TimecodeTrackProvider(); - virtual bool possiblyContainsTrack( const KUrl &url ) const; - virtual Meta::TrackPtr trackForUrl( const KUrl &url ); + virtual bool possiblyContainsTrack( const QUrl &url ) const; + virtual Meta::TrackPtr trackForUrl( const QUrl &url ); }; #endif diff --git a/src/core-impl/playlists/types/file/PlaylistFile.cpp b/src/core-impl/playlists/types/file/PlaylistFile.cpp index fd9e287bb3..7b0902bd7c 100644 --- a/src/core-impl/playlists/types/file/PlaylistFile.cpp +++ b/src/core-impl/playlists/types/file/PlaylistFile.cpp @@ -1,187 +1,188 @@ /**************************************************************************************** * Copyright (c) 2011 Bart Cerneels * * * * 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, see . * ****************************************************************************************/ #include "PlaylistFile.h" #include "core/support/Debug.h" #include "core-impl/playlists/types/file/PlaylistFileLoaderJob.h" #include "playlistmanager/file/PlaylistFileProvider.h" #include "playlistmanager/PlaylistManager.h" -#include +#include #include #include using namespace Playlists; -PlaylistFile::PlaylistFile( const KUrl &url, PlaylistProvider *provider ) +PlaylistFile::PlaylistFile( const QUrl &url, PlaylistProvider *provider ) : Playlist() , m_provider( provider ) , m_url( url ) , m_tracksLoaded( false ) , m_name( m_url.fileName() ) , m_relativePaths( false ) , m_loadingDone( 0 ) { } void PlaylistFile::saveLater() { PlaylistFileProvider *fileProvider = qobject_cast( m_provider ); if( !fileProvider ) return; fileProvider->saveLater( PlaylistFilePtr( this ) ); } void PlaylistFile::triggerTrackLoad() { if( m_tracksLoaded ) { notifyObserversTracksLoaded(); return; } PlaylistFileLoaderJob *worker = new PlaylistFileLoaderJob( PlaylistFilePtr( this ) ); ThreadWeaver::Weaver::instance()->enqueue( worker ); if ( !isLoadingAsync() ) m_loadingDone.acquire(); // after loading is finished worker will release semapore } bool PlaylistFile::isWritable() const { if( m_url.isEmpty() ) return false; return QFileInfo( m_url.path() ).isWritable(); } int PlaylistFile::trackCount() const { if( m_tracksLoaded ) return m_tracks.count(); else return -1; } void PlaylistFile::addTrack( Meta::TrackPtr track, int position ) { if( !track ) // playlists might contain invalid tracks. see BUG: 303056 return; int trackPos = position < 0 ? m_tracks.count() : position; if( trackPos > m_tracks.count() ) trackPos = m_tracks.count(); m_tracks.insert( trackPos, track ); // set in case no track was in the playlist before m_tracksLoaded = true; notifyObserversTrackAdded( track, trackPos ); if( !m_url.isEmpty() ) saveLater(); } void PlaylistFile::removeTrack( int position ) { if( position < 0 || position >= m_tracks.count() ) return; m_tracks.removeAt( position ); notifyObserversTrackRemoved( position ); if( !m_url.isEmpty() ) saveLater(); } bool PlaylistFile::save( bool relative ) { m_relativePaths = relative; QMutexLocker locker( &m_saveLock ); //if the location is a directory append the name of this playlist. - if( m_url.fileName( KUrl::ObeyTrailingSlash ).isNull() ) + if( m_url.fileName( QUrl::ObeyTrailingSlash ).isNull() ) m_url.setFileName( name() ); QFile file( m_url.path() ); if( !file.open( QIODevice::WriteOnly ) ) { warning() << QString( "Cannot write playlist (%1)." ).arg( file.fileName() ) << file.errorString(); return false; } savePlaylist( file ); file.close(); return true; } void PlaylistFile::setName( const QString &name ) { //can't save to a new file if we don't know where. if( !m_url.isEmpty() && !name.isEmpty() ) { QString exten = QString( ".%1" ).arg(extension()); m_url.setFileName( name + ( name.endsWith( exten, Qt::CaseInsensitive ) ? "" : exten ) ); } } void PlaylistFile::addProxyTrack( const Meta::TrackPtr &proxyTrack ) { m_tracks << proxyTrack; notifyObserversTrackAdded( m_tracks.last(), m_tracks.size() - 1 ); } -KUrl -PlaylistFile::getAbsolutePath( const KUrl &url ) +QUrl +PlaylistFile::getAbsolutePath( const QUrl &url ) { - KUrl absUrl = url; + QUrl absUrl = url; if( url.isRelative() ) { m_relativePaths = true; - // example: url = KUrl("../tunes/tune.ogg") + // example: url = QUrl("../tunes/tune.ogg") const QString relativePath = url.path(); // "../tunes/tune.ogg" absUrl = m_url.directory(); // file:///playlists/ - absUrl.addPath( relativePath ); // file:///playlists/../tunes/tune.ogg + absUrl = absUrl.adjusted(QUrl::StripTrailingSlash); + absUrl.setPath(absUrl.path() + '/' + ( relativePath )); absUrl.cleanPath(); // file:///playlists/tunes/tune.ogg } return absUrl; } QString PlaylistFile::trackLocation( const Meta::TrackPtr &track ) const { - KUrl path = track->playableUrl(); + QUrl path = track->playableUrl(); if( path.isEmpty() ) return track->uidUrl(); if( !m_relativePaths || m_url.isEmpty() || !path.isLocalFile() || !m_url.isLocalFile() ) return path.toEncoded(); QDir playlistDir( m_url.directory() ); return QUrl::toPercentEncoding( playlistDir.relativeFilePath( path.path() ), "/" ); } diff --git a/src/core-impl/playlists/types/file/PlaylistFile.h b/src/core-impl/playlists/types/file/PlaylistFile.h index cbc790ec3a..20f0e39320 100644 --- a/src/core-impl/playlists/types/file/PlaylistFile.h +++ b/src/core-impl/playlists/types/file/PlaylistFile.h @@ -1,155 +1,155 @@ /**************************************************************************************** * Copyright (c) 2009-2011 Bart Cerneels * * * * 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, see . * ****************************************************************************************/ #ifndef METAPLAYLISTFILE_H #define METAPLAYLISTFILE_H #include "amarok_export.h" #include "core/playlists/Playlist.h" #include "core/meta/forward_declarations.h" #include "core-impl/meta/proxy/MetaProxy.h" #include #include class QFile; namespace Playlists { class PlaylistProvider; class PlaylistFile; class PlaylistFileLoaderJob; typedef KSharedPtr PlaylistFilePtr; typedef QList PlaylistFileList; /** * Base class for all playlist files **/ class AMAROK_EXPORT PlaylistFile : public Playlist { friend class PlaylistFileLoaderJob; public: /* Playlist methods */ - virtual KUrl uidUrl() const { return m_url; } + virtual QUrl uidUrl() const { return m_url; } virtual QString name() const { return m_url.fileName(); } virtual Meta::TrackList tracks() { return m_tracks; } virtual int trackCount() const; virtual void addTrack( Meta::TrackPtr track, int position ); virtual void removeTrack( int position ); virtual void triggerTrackLoad(); /** * Overrides filename */ virtual void setName( const QString &name ); virtual PlaylistProvider *provider() const { return m_provider; } /* PlaylistFile methods */ virtual QList queue() { return QList(); } virtual void setQueue( const QList &rows ) { Q_UNUSED( rows ); } /** * Returns file extension which is corresponding to the playlist type */ virtual QString extension() const = 0; /** * Returns mime type of this playlist file */ virtual QString mimetype() const = 0; virtual bool isWritable() const; /** * Saves the playlist to underlying file immediatelly. * * @param relative whether to use relative paths to track in the file */ bool save( bool relative ); /** * Adds tracks to internal store. * * @note in order to save tracks to file, save method should be called **/ virtual void addTracks( const Meta::TrackList &tracks ) { m_tracks += tracks; } virtual void setGroups( const QStringList &groups ) { m_groups = groups; } virtual QStringList groups() { return m_groups; } protected: - PlaylistFile( const KUrl &url, PlaylistProvider *provider ); + PlaylistFile( const QUrl &url, PlaylistProvider *provider ); /** * Schedule this playlist file to be saved on the next iteration of the * mainloop. Useful in addTrack() and removeTrack() functions. */ void saveLater(); /** * Actual file-specific implementation of playlist saving. */ virtual void savePlaylist( QFile &file ) = 0; /** * Appends MetaProxy::Track* to m_tracks and invokes notifyObserversTrackAdded() */ void addProxyTrack( const Meta::TrackPtr &proxyTrack ); /** * Loads playlist from the stream. * @returns true if the loading was successful. */ virtual bool load( QTextStream &stream ) = 0; /** * Loads playlist from QByteArray in order to postpone encoding detection procedure * @returns true if the loading was successful. */ virtual bool load( QByteArray &content ) { QTextStream stream( &content ); return load( stream ); } /** Normalizes track location */ QString trackLocation( const Meta::TrackPtr &track ) const; /** * If the passed url is relative, this method convert given url to absolute. * For example, "tunes/tune.ogg" gets converted to "file:///playlists/tunes/tune.ogg" * Sets m_relative to true if it ecounters a relative url * (this serves to preserve playlist "relativity" across reads & saves) **/ - KUrl getAbsolutePath( const KUrl &url ); + QUrl getAbsolutePath( const QUrl &url ); PlaylistProvider *m_provider; QStringList m_groups; - KUrl m_url; + QUrl m_url; mutable bool m_tracksLoaded; mutable Meta::TrackList m_tracks; QString m_name; /** true if tracks path are relative */ bool m_relativePaths; QMutex m_saveLock; /** allows to wait for end of loading */ QSemaphore m_loadingDone; }; } Q_DECLARE_METATYPE( Playlists::PlaylistFilePtr ) Q_DECLARE_METATYPE( Playlists::PlaylistFileList ) #endif diff --git a/src/core-impl/playlists/types/file/PlaylistFileLoaderJob.cpp b/src/core-impl/playlists/types/file/PlaylistFileLoaderJob.cpp index 75f4d01b27..ed3fcc3bfe 100644 --- a/src/core-impl/playlists/types/file/PlaylistFileLoaderJob.cpp +++ b/src/core-impl/playlists/types/file/PlaylistFileLoaderJob.cpp @@ -1,118 +1,118 @@ /**************************************************************************************** * Copyright (c) 2013 Tatjana Gornak * * * * 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, see . * ****************************************************************************************/ #include "core-impl/playlists/types/file/PlaylistFileLoaderJob.h" #include "core/meta/Meta.h" #include "core/playlists/PlaylistFormat.h" #include "core/interfaces/Logger.h" #include "core/support/Amarok.h" #include "core/support/Components.h" #include "core/support/Debug.h" #include "core/support/SemaphoreReleaser.h" #include #include -#include +#include #include #include #include #include #include using namespace Playlists; PlaylistFileLoaderJob::PlaylistFileLoaderJob( const PlaylistFilePtr &playlist ) : m_playlist( playlist ) { connect( this, SIGNAL(done(ThreadWeaver::Job*)), this, SLOT(slotDone()) ); // we must handle remove downloading here as KIO is coupled with GUI as is not // designed to work from another thread - const KUrl url = playlist->uidUrl(); + const QUrl url = playlist->uidUrl(); if( url.isLocalFile() ) { m_actualPlaylistFile = url.toLocalFile(); m_downloadSemaphore.release(); // pretend file "already downloaded" } else { m_tempFile.setSuffix( '.' + Amarok::extension( url.url() ) ); if( !m_tempFile.open() ) { Amarok::Components::logger()->longMessage( i18n( "Could not create a temporary file to download playlist." ) ); m_downloadSemaphore.release(); // prevent deadlock return; } KIO::FileCopyJob *job = KIO::file_copy( url , m_tempFile.fileName(), 0774, KIO::Overwrite | KIO::HideProgressInfo ); Amarok::Components::logger()->newProgressOperation( job, i18n("Downloading remote playlist" ) ); if( playlist->isLoadingAsync() ) // job is started automatically by KIO connect( job, SIGNAL(finished(KJob*)), SLOT(slotDonwloadFinished(KJob*)) ); else { job->exec(); slotDonwloadFinished( job ); } } } void PlaylistFileLoaderJob::run() { SemaphoreReleaser releaser( m_playlist->isLoadingAsync() ? 0 : &m_playlist->m_loadingDone ); m_downloadSemaphore.acquire(); // wait for possible download to finish if( m_actualPlaylistFile.isEmpty() ) return; // previous error, already reported QFile file( m_actualPlaylistFile ); if( !file.open( QIODevice::ReadOnly | QIODevice::Text ) ) { using namespace Amarok; Components::logger()->longMessage( i18nc( "%1 is file path", "Cannot read playlist from %1", m_actualPlaylistFile ), Logger::Error ); return; } QByteArray content = file.readAll(); file.close(); m_playlist->load( content ); } void PlaylistFileLoaderJob::slotDonwloadFinished( KJob *job ) { if( job->error() ) { using namespace Amarok; warning() << job->errorString(); } else m_actualPlaylistFile = m_tempFile.fileName(); m_downloadSemaphore.release(); } void PlaylistFileLoaderJob::slotDone() { m_playlist->notifyObserversTracksLoaded(); deleteLater(); } diff --git a/src/core-impl/playlists/types/file/PlaylistFileSupport.cpp b/src/core-impl/playlists/types/file/PlaylistFileSupport.cpp index 3fbde458a6..3ee41899f9 100644 --- a/src/core-impl/playlists/types/file/PlaylistFileSupport.cpp +++ b/src/core-impl/playlists/types/file/PlaylistFileSupport.cpp @@ -1,158 +1,160 @@ /**************************************************************************************** * Copyright (c) 2007 Ian Monroe * * * * 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) version 3 or * * any later version accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * 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, see . * ****************************************************************************************/ #include "core/playlists/PlaylistFormat.h" #include "core/interfaces/Logger.h" #include "core/support/Components.h" #include "core/support/Amarok.h" #include "core-impl/playlists/types/file/PlaylistFileSupport.h" #include "core/support/Debug.h" #include "core-impl/playlists/types/file/asx/ASXPlaylist.h" #include "core-impl/playlists/types/file/xspf/XSPFPlaylist.h" #include "core-impl/playlists/types/file/pls/PLSPlaylist.h" #include "core-impl/playlists/types/file/m3u/M3UPlaylist.h" #include "playlistmanager/file/PlaylistFileProvider.h" #include "amarokconfig.h" #include #include -#include +#include #include #include #include using namespace Playlists; PlaylistFilePtr -Playlists::loadPlaylistFile( const KUrl &url, PlaylistFileProvider *provider ) +Playlists::loadPlaylistFile( const QUrl &url, PlaylistFileProvider *provider ) { // note: this function can be called from out of process, so don't do any // UI stuff from this thread. if( !url.isValid() ) { error() << "url is not valid!"; return PlaylistFilePtr(); } if( url.isLocalFile() ) { if( !QFileInfo( url.toLocalFile() ).exists() ) { error() << QString("Could not load local playlist file %1!").arg( url.toLocalFile() ); return PlaylistFilePtr(); } } PlaylistFormat format = Playlists::getFormat( url ); PlaylistFilePtr playlist; switch( format ) { case ASX: playlist = new ASXPlaylist( url, provider ); break; case PLS: playlist = new PLSPlaylist( url, provider ); break; case M3U: playlist = new M3UPlaylist( url, provider ); break; case XSPF: playlist = new XSPFPlaylist( url, provider ); break; default: debug() << "Could not load playlist file " << url; break; } return playlist; } bool -Playlists::exportPlaylistFile( const Meta::TrackList &list, const KUrl &path, bool relative, +Playlists::exportPlaylistFile( const Meta::TrackList &list, const QUrl &path, bool relative, const QList &queued ) { PlaylistFormat format = Playlists::getFormat( path ); bool result = false; PlaylistFilePtr playlist; switch( format ) { case ASX: playlist = new ASXPlaylist( path.toLocalFile() ); break; case PLS: playlist = new PLSPlaylist( path.toLocalFile() ); break; case M3U: playlist = new M3UPlaylist( path.toLocalFile() ); break; case XSPF: playlist = new XSPFPlaylist( path.toLocalFile() ); break; default: debug() << "Could not export playlist file " << path; break; } if( playlist ) { playlist->addTracks( list ); playlist->setQueue( queued ); result = playlist->save( relative ); } else { KMessageBox::error( 0, i18n( "The used file extension is not valid for playlists." ), i18n( "Unknown playlist format" ) ); } return result; } bool Playlists::canExpand( Meta::TrackPtr track ) { if( !track ) return false; return Playlists::getFormat( track->uidUrl() ) != Playlists::NotPlaylist; } PlaylistPtr Playlists::expand( Meta::TrackPtr track ) { return Playlists::PlaylistPtr::dynamicCast( loadPlaylistFile( track->uidUrl() ) ); } -KUrl +QUrl Playlists::newPlaylistFilePath( const QString &fileExtension ) { int trailingNumber = 1; KLocalizedString fileName = ki18n("Playlist_%1"); - KUrl url( Amarok::saveLocation( "playlists" ) ); - url.addPath( fileName.subs( trailingNumber ).toString() ); + QUrl url( Amarok::saveLocation( "playlists" ) ); + url = url.adjusted(QUrl::StripTrailingSlash); + url.setPath(url.path() + '/' + ( fileName.subs( trailingNumber ).toString() )); while( QFileInfo( url.path() ).exists() ) - url.setFileName( fileName.subs( ++trailingNumber ).toString() ); + url = url.adjusted(QUrl::RemoveFilename); + url.setPath(url.path() + fileName.subs( ++trailingNumber ).toString() ); - return KUrl( QString( "%1.%2" ).arg( url.path(), fileExtension ) ); + return QUrl( QString( "%1.%2" ).arg( url.path(), fileExtension ) ); } diff --git a/src/core-impl/playlists/types/file/PlaylistFileSupport.h b/src/core-impl/playlists/types/file/PlaylistFileSupport.h index 8335a24076..f7b07968dd 100644 --- a/src/core-impl/playlists/types/file/PlaylistFileSupport.h +++ b/src/core-impl/playlists/types/file/PlaylistFileSupport.h @@ -1,44 +1,44 @@ /**************************************************************************************** * Copyright (c) 2007 Ian Monroe * * * * 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, see . * ****************************************************************************************/ #ifndef AMAROK_META_PLAYLISTFILESUPPORT_H #define AMAROK_META_PLAYLISTFILESUPPORT_H #include "amarok_export.h" #include "core/meta/forward_declarations.h" #include "core-impl/playlists/types/file/PlaylistFile.h" namespace Playlists { class PlaylistFileProvider; - AMAROK_EXPORT PlaylistFilePtr loadPlaylistFile( const KUrl &url, PlaylistFileProvider *provider = 0 ); + AMAROK_EXPORT PlaylistFilePtr loadPlaylistFile( const QUrl &url, PlaylistFileProvider *provider = 0 ); - bool exportPlaylistFile( const Meta::TrackList &list, const KUrl &path, bool relative = false, + bool exportPlaylistFile( const Meta::TrackList &list, const QUrl &path, bool relative = false, const QList &queued = QList() ); /* HACK: * the next two functions are needed to support some services that have no other way * of presenting data to the user than wrapping the url to a playlist in a track. */ bool canExpand( Meta::TrackPtr track ); PlaylistPtr expand( Meta::TrackPtr track ); - AMAROK_EXPORT KUrl newPlaylistFilePath( const QString &fileExtension ); + AMAROK_EXPORT QUrl newPlaylistFilePath( const QString &fileExtension ); } #endif diff --git a/src/core-impl/playlists/types/file/asx/ASXPlaylist.cpp b/src/core-impl/playlists/types/file/asx/ASXPlaylist.cpp index 1d1b3ce5c7..6691a20a6b 100644 --- a/src/core-impl/playlists/types/file/asx/ASXPlaylist.cpp +++ b/src/core-impl/playlists/types/file/asx/ASXPlaylist.cpp @@ -1,192 +1,192 @@ /**************************************************************************************** * Copyright (c) 2013 Tatjana Gornak * * * * 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, see . * ****************************************************************************************/ #include "ASXPlaylist.h" #include "core/capabilities/StreamInfoCapability.h" #include "core/support/Debug.h" #include "core-impl/meta/proxy/MetaProxy.h" #include "core-impl/playlists/types/file/xspf/XSPFPlaylist.h" -#include +#include #include #include using namespace Playlists; -ASXPlaylist::ASXPlaylist( const KUrl &url, PlaylistProvider *provider ) +ASXPlaylist::ASXPlaylist( const QUrl &url, PlaylistProvider *provider ) : PlaylistFile( url, provider ) , QDomDocument() { } void ASXPlaylist::savePlaylist( QFile &file ) { QTextStream stream( &file ); stream.setCodec( "UTF-8" ); writeTrackList(); QDomDocument::save( stream, 2 /*indent*/, QDomNode::EncodingFromTextStream ); } bool ASXPlaylist::processContent( QTextStream &stream ) { QString errorMsg; int errorLine, errorColumn; QString data = stream.readAll(); // ASX looks a lot like xml, but doesn't require tags to be case sensitive, // meaning we have to accept things like: ... // We use a dirty way to achieve this: we make all tags lower case QRegExp tagPattern( "(<[/]?[^>]*[A-Z]+[^>]*>)", Qt::CaseInsensitive ); QRegExp urlPattern( "(href\\s*=\\s*\")([^\"]+)\"", Qt::CaseInsensitive ); int index = 0; while ( ( index = tagPattern.indexIn( data, index ) ) != -1 ) { QString original = tagPattern.cap( 1 ).toLocal8Bit(); QString tagReplacement = tagPattern.cap( 1 ).toLower().toLocal8Bit(); if ( urlPattern.indexIn( original, 0 ) != -1 ) { // Some playlists have unescaped & characters in URLs QString url = urlPattern.cap( 2 ); url.replace( QRegExp( "&(?!amp;|quot;|apos;|lt;|gt;)" ), "&" ); QString urlReplacement = urlPattern.cap( 1 ) % url % "\""; tagReplacement.replace( urlPattern.cap(0).toLocal8Bit().toLower(), urlReplacement.toLocal8Bit() ); } data.replace( original, tagReplacement ); index += tagPattern.matchedLength(); } if( !setContent( data, &errorMsg, &errorLine, &errorColumn ) ) { error() << "Error loading xml file: " "(" << errorMsg << ")" << " at line " << errorLine << ", column " << errorColumn; m_tracksLoaded = false; } else m_tracksLoaded = true; return m_tracksLoaded; } bool ASXPlaylist::loadAsx( QTextStream &stream ) { if ( !processContent( stream ) ) return false; QDomNode asx = documentElement(); QDomNode subNode = asx.firstChild(); QDomNode subSubNode; while( !subNode.isNull() ) { XSPFTrack track; subSubNode = subNode.firstChild(); if( subNode.nodeName() == "entry" ) { while( !subSubNode.isNull() ) { if( subSubNode.nodeName() == "ref" ) { QByteArray path = subSubNode.attributes().namedItem("href").nodeValue().toUtf8(); path.replace( '\\', '/' ); - KUrl url = getAbsolutePath( KUrl::fromEncoded( path ) ); + QUrl url = getAbsolutePath( QUrl::fromEncoded( path ) ); track.location = url; } else if( subSubNode.nodeName() == "title" ) track.title = subSubNode.firstChild().nodeValue(); else if( subSubNode.nodeName() == "author" ) track.creator = subSubNode.firstChild().nodeValue(); subSubNode = subSubNode.nextSibling(); } } MetaProxy::Track *proxyTrack = new MetaProxy::Track( track.location ); proxyTrack->setTitle( track.title ); proxyTrack->setArtist( track.creator ); proxyTrack->setLength( track.duration ); m_tracks << Meta::TrackPtr( proxyTrack ); subNode = subNode.nextSibling(); } return true; } void ASXPlaylist::writeTrackList( ) { Meta::TrackList trackList = tracks(); if ( documentElement().namedItem( "asx" ).isNull() ) { QDomElement root = createElement( "asx" ); root.setAttribute( "version", 3.0 ); appendChild( root ); } foreach( Meta::TrackPtr track, trackList ) { QDomNode subNode = createElement( "entry" ); //URI of resource to be rendered. QDomElement location = createElement( "ref" ); //Track title QDomNode title = createElement( "title" ); //Human-readable name of the entity that authored the resource. QDomNode creator = createElement( "author" ); //Description of a track QDomNode abstact = createElement( "abstract" ); location.setAttribute( "href", trackLocation( track ) ); subNode.appendChild( location ); #define APPENDNODE( X, Y ) \ { \ X.appendChild( createTextNode( Y ) ); \ subNode.appendChild( X ); \ } Capabilities::StreamInfoCapability *streamInfo = track->create(); if( streamInfo ) // We have a stream, use it's metadata instead of the tracks. { if( !streamInfo->streamName().isEmpty() ) APPENDNODE( title, streamInfo->streamName() ); if( !streamInfo->streamSource().isEmpty() ) APPENDNODE( creator, streamInfo->streamSource() ); delete streamInfo; } else { if( !track->name().isEmpty() ) APPENDNODE( title, track->name() ); if( track->artist() && !track->artist()->name().isEmpty() ) APPENDNODE( creator, track->artist()->name() ); } if( !track->comment().isEmpty() ) APPENDNODE(abstact, track->comment() ); #undef APPENDNODE documentElement().appendChild( subNode ); } } diff --git a/src/core-impl/playlists/types/file/asx/ASXPlaylist.h b/src/core-impl/playlists/types/file/asx/ASXPlaylist.h index b038757747..c840835a5c 100644 --- a/src/core-impl/playlists/types/file/asx/ASXPlaylist.h +++ b/src/core-impl/playlists/types/file/asx/ASXPlaylist.h @@ -1,47 +1,47 @@ /**************************************************************************************** * Copyright (c) 2013 Tatjana Gornak * * 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, see . * ****************************************************************************************/ #ifndef ASXPLAYLIST_H #define ASXPLAYLIST_H #include "core-impl/playlists/types/file/PlaylistFile.h" #include namespace Playlists { /** * TODO: Use QDomDocument locally just in saving and loading methods. */ class AMAROK_EXPORT ASXPlaylist : public PlaylistFile, public QDomDocument { public: - ASXPlaylist( const KUrl &url, PlaylistProvider *provider = 0 ); + ASXPlaylist( const QUrl &url, PlaylistProvider *provider = 0 ); virtual bool save( bool relative ) { return PlaylistFile::save( relative ); } using PlaylistFile::load; virtual bool load( QTextStream &stream ) { return loadAsx( stream ); } virtual QString extension() const { return "asx"; } virtual QString mimetype() const { return "video/x-ms-asf"; } protected: bool loadAsx( QTextStream &stream ); /** Writes tracks to file */ void writeTrackList(); virtual void savePlaylist( QFile &file ); bool processContent( QTextStream &stream ); }; } #endif diff --git a/src/core-impl/playlists/types/file/m3u/M3UPlaylist.cpp b/src/core-impl/playlists/types/file/m3u/M3UPlaylist.cpp index 28e3b00065..3d0ea4cc0b 100644 --- a/src/core-impl/playlists/types/file/m3u/M3UPlaylist.cpp +++ b/src/core-impl/playlists/types/file/m3u/M3UPlaylist.cpp @@ -1,116 +1,116 @@ /**************************************************************************************** * Copyright (c) 2007 Bart Cerneels * * * * 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, see . * ****************************************************************************************/ #include "M3UPlaylist.h" #include "core/support/Debug.h" #include using namespace Playlists; -M3UPlaylist::M3UPlaylist( const KUrl &url, PlaylistProvider *provider ) +M3UPlaylist::M3UPlaylist( const QUrl &url, PlaylistProvider *provider ) : PlaylistFile( url, provider ) { } bool M3UPlaylist::loadM3u( QTextStream &stream ) { if( m_tracksLoaded ) return true; const QString directory = m_url.directory(); m_tracksLoaded = true; int length = -1; QString extinfTitle; do { QString line = stream.readLine(); if( line.startsWith( "#EXTINF" ) ) { const QString extinf = line.section( ':', 1 ); bool ok; length = extinf.section( ',', 0, 0 ).toInt( &ok ); if( !ok ) length = -1; extinfTitle = extinf.section( ',', 1 ); } else if( !line.startsWith( '#' ) && !line.isEmpty() ) { line = line.replace( "\\", "/" ); - KUrl url = getAbsolutePath( KUrl( line ) ); + QUrl url = getAbsolutePath( QUrl( line ) ); MetaProxy::TrackPtr proxyTrack( new MetaProxy::Track( url ) ); QString artist = extinfTitle.section( " - ", 0, 0 ); QString title = extinfTitle.section( " - ", 1, 1 ); //if title and artist are saved such as in M3UPlaylist::save() if( !title.isEmpty() && !artist.isEmpty() ) { proxyTrack->setTitle( title ); proxyTrack->setArtist( artist ); } else { proxyTrack->setTitle( extinfTitle ); } proxyTrack->setLength( length ); Meta::TrackPtr track( proxyTrack.data() ); addProxyTrack( track ); } } while( !stream.atEnd() ); //TODO: return false if stream is not readable, empty or has errors return true; } void M3UPlaylist::savePlaylist( QFile &file ) { QTextStream stream( &file ); stream << "#EXTM3U\n"; - KUrl::List urls; + QList urls; QStringList titles; QList lengths; foreach( const Meta::TrackPtr &track, m_tracks ) { if( !track ) // see BUG: 303056 continue; - const KUrl &url = track->playableUrl(); + const QUrl &url = track->playableUrl(); int length = track->length() / 1000; const QString &title = track->name(); const QString &artist = track->artist()->name(); if( !title.isEmpty() && !artist.isEmpty() && length ) { stream << "#EXTINF:"; stream << QString::number( length ); stream << ','; stream << artist << " - " << title; stream << '\n'; } - if( url.protocol() == "file" ) + if( url.scheme() == "file" ) stream << trackLocation( track ); else stream << url.url(); stream << "\n"; } } diff --git a/src/core-impl/playlists/types/file/m3u/M3UPlaylist.h b/src/core-impl/playlists/types/file/m3u/M3UPlaylist.h index 08a434ec35..f39f654ad2 100644 --- a/src/core-impl/playlists/types/file/m3u/M3UPlaylist.h +++ b/src/core-impl/playlists/types/file/m3u/M3UPlaylist.h @@ -1,46 +1,46 @@ /**************************************************************************************** * Copyright (c) 2007 Bart Cerneels * * * * 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, see . * ****************************************************************************************/ #ifndef METAM3UPLAYLIST_H #define METAM3UPLAYLIST_H #include "core-impl/playlists/types/file/PlaylistFile.h" namespace Playlists { /** * @author Bart Cerneels */ class AMAROK_EXPORT M3UPlaylist : public PlaylistFile { public: - M3UPlaylist( const KUrl &url, PlaylistProvider *provider = 0 ); + M3UPlaylist( const QUrl &url, PlaylistProvider *provider = 0 ); /* PlaylistFile methods */ using PlaylistFile::load; virtual bool load( QTextStream &stream ) { return loadM3u( stream ); } virtual QString extension() const { return "m3u"; } virtual QString mimetype() const { return "audio/x-mpegurl"; } protected: virtual void savePlaylist( QFile &file ); private: bool loadM3u( QTextStream &stream ); }; } #endif diff --git a/src/core-impl/playlists/types/file/pls/PLSPlaylist.cpp b/src/core-impl/playlists/types/file/pls/PLSPlaylist.cpp index a120ae2e7b..5c237f0135 100644 --- a/src/core-impl/playlists/types/file/pls/PLSPlaylist.cpp +++ b/src/core-impl/playlists/types/file/pls/PLSPlaylist.cpp @@ -1,216 +1,216 @@ /**************************************************************************************** * Copyright (c) 2007 Bart Cerneels * * * * 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, see . * ****************************************************************************************/ #include "PLSPlaylist.h" #include "core/support/Debug.h" #include using namespace Playlists; -PLSPlaylist::PLSPlaylist( const KUrl &url, PlaylistProvider *provider ) +PLSPlaylist::PLSPlaylist( const QUrl &url, PlaylistProvider *provider ) : PlaylistFile( url, provider ) { } bool PLSPlaylist::loadPls( QTextStream &textStream ) { if( m_tracksLoaded ) return true; m_tracksLoaded = true; // Counted number of "File#=" lines. unsigned int entryCnt = 0; // Value of the "NumberOfEntries=#" line. unsigned int numberOfEntries = 0; // Does the file have a "[playlist]" section? (as it's required by the standard) bool havePlaylistSection = false; QString tmp; QStringList lines; QRegExp regExp_NumberOfEntries("^NumberOfEntries\\s*=\\s*\\d+$"); regExp_NumberOfEntries.setCaseSensitivity( Qt::CaseInsensitive ); // It seems many playlists use numberofentries const QRegExp regExp_File("^File\\d+\\s*="); const QRegExp regExp_Title("^Title\\d+\\s*="); const QRegExp regExp_Length("^Length\\d+\\s*=\\s*-?\\d+$"); // Length Can be -1 const QRegExp regExp_Version("^Version\\s*=\\s*\\d+$"); const QString section_playlist("[playlist]"); /* Preprocess the input data. * Read the lines into a buffer; Cleanup the line strings; * Count the entries manually and read "NumberOfEntries". */ while( !textStream.atEnd() ) { tmp = textStream.readLine(); tmp = tmp.trimmed(); if( tmp.isEmpty() ) continue; lines.append( tmp ); if( tmp.contains( regExp_File ) ) { entryCnt++; continue; } if( tmp == section_playlist ) { havePlaylistSection = true; continue; } if( tmp.contains( regExp_NumberOfEntries ) ) { numberOfEntries = tmp.section( '=', -1 ).trimmed().toUInt(); continue; } } if( numberOfEntries != entryCnt ) { warning() << ".pls playlist: Invalid \"NumberOfEntries\" value. " << "NumberOfEntries=" << numberOfEntries << " counted=" << entryCnt << endl; /* Corrupt file. The "NumberOfEntries" value is * not correct. Fix it by setting it to the manually * counted number and go on parsing. */ numberOfEntries = entryCnt; } if( numberOfEntries == 0 ) { return true; } unsigned int index; bool ok = false; bool inPlaylistSection = false; MetaProxy::TrackPtr proxyTrack; /* Now iterate through all beautified lines in the buffer * and parse the playlist data. */ QStringList::const_iterator i = lines.constBegin(), end = lines.constEnd(); for( ; i != end; ++i ) { if( !inPlaylistSection && havePlaylistSection ) { /* The playlist begins with the "[playlist]" tag. * Skip everything before this. */ if( (*i) == section_playlist ) inPlaylistSection = true; continue; } if( (*i).contains( regExp_File ) ) { // Have a "File#=XYZ" line. index = loadPls_extractIndex( *i ); if( index > numberOfEntries || index == 0 ) continue; tmp = (*i).section( '=', 1 ).trimmed(); - KUrl url = getAbsolutePath( KUrl( tmp ) ); + QUrl url = getAbsolutePath( QUrl( tmp ) ); proxyTrack = new MetaProxy::Track( url ); Meta::TrackPtr track( proxyTrack.data() ); addProxyTrack( track ); continue; } if( (*i).contains(regExp_Title) && proxyTrack ) { // Have a "Title#=XYZ" line. index = loadPls_extractIndex(*i); if( index > numberOfEntries || index == 0 ) continue; tmp = (*i).section( '=', 1 ).trimmed(); proxyTrack->setTitle( tmp ); continue; } if( (*i).contains( regExp_Length ) && proxyTrack ) { // Have a "Length#=XYZ" line. index = loadPls_extractIndex(*i); if( index > numberOfEntries || index == 0 ) continue; tmp = (*i).section( '=', 1 ).trimmed(); bool ok = false; int seconds = tmp.toInt( &ok ); if( ok ) proxyTrack->setLength( seconds * 1000 ); //length is in milliseconds continue; } if( (*i).contains( regExp_NumberOfEntries ) ) { // Have the "NumberOfEntries=#" line. continue; } if( (*i).contains( regExp_Version ) ) { // Have the "Version=#" line. tmp = (*i).section( '=', 1 ).trimmed(); // We only support Version=2 if (tmp.toUInt( &ok ) != 2) warning() << ".pls playlist: Unsupported version." << endl; continue; } warning() << ".pls playlist: Unrecognized line: \"" << *i << "\"" << endl; } return true; } unsigned int PLSPlaylist::loadPls_extractIndex( const QString &str ) const { /* Extract the index number out of a .pls line. * Example: * loadPls_extractIndex("File2=foobar") == 2 */ bool ok = false; unsigned int ret; QString tmp( str.section( '=', 0, 0 ) ); tmp.remove( QRegExp( "^\\D*" ) ); ret = tmp.trimmed().toUInt( &ok ); Q_ASSERT(ok); return ret; } void PLSPlaylist::savePlaylist( QFile &file ) { //Format: http://en.wikipedia.org/wiki/PLS_(file_format) QTextStream stream( &file ); //header stream << "[Playlist]\n"; //body int i = 1; //PLS starts at File1= foreach( Meta::TrackPtr track, m_tracks ) { if( !track ) // see BUG: 303056 continue; stream << "File" << i << "=" << trackLocation( track ); stream << "\nTitle" << i << "="; stream << track->name(); stream << "\nLength" << i << "="; stream << track->length() / 1000; stream << "\n"; i++; } //footer stream << "NumberOfEntries=" << m_tracks.count() << endl; stream << "Version=2\n"; } diff --git a/src/core-impl/playlists/types/file/pls/PLSPlaylist.h b/src/core-impl/playlists/types/file/pls/PLSPlaylist.h index 74d2fad302..054dfd8d1b 100644 --- a/src/core-impl/playlists/types/file/pls/PLSPlaylist.h +++ b/src/core-impl/playlists/types/file/pls/PLSPlaylist.h @@ -1,47 +1,47 @@ /**************************************************************************************** * Copyright (c) 2007 Bart Cerneels * * * * 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, see . * ****************************************************************************************/ #ifndef METAPLSPLAYLIST_H #define METAPLSPLAYLIST_H #include "core-impl/playlists/types/file/PlaylistFile.h" namespace Playlists { /** * @author Bart Cerneels */ class AMAROK_EXPORT PLSPlaylist : public PlaylistFile { public: - PLSPlaylist( const KUrl &url, PlaylistProvider *provider = 0 ); + PLSPlaylist( const QUrl &url, PlaylistProvider *provider = 0 ); /* PlaylistFile methods */ using PlaylistFile::load; virtual bool load( QTextStream &stream ) { return loadPls( stream ); } virtual QString extension() const { return "pls"; } virtual QString mimetype() const { return "audio/x-scpls"; } protected: virtual void savePlaylist( QFile &file ); private: bool loadPls( QTextStream &stream ); unsigned int loadPls_extractIndex( const QString &str ) const; }; } #endif diff --git a/src/core-impl/playlists/types/file/xspf/XSPFPlaylist.cpp b/src/core-impl/playlists/types/file/xspf/XSPFPlaylist.cpp index 4bdb9b96c4..17ca858641 100644 --- a/src/core-impl/playlists/types/file/xspf/XSPFPlaylist.cpp +++ b/src/core-impl/playlists/types/file/xspf/XSPFPlaylist.cpp @@ -1,680 +1,680 @@ /**************************************************************************************** * Copyright (c) 2006 Mattias Fliesberg * * Copyright (c) 2007 Ian Monroe * * Copyright (c) 2007 Bart Cerneels * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "XSPFPlaylist" #include "XSPFPlaylist.h" #include "core/capabilities/StreamInfoCapability.h" #include "core/support/Debug.h" #include "core-impl/collections/support/CollectionManager.h" #include "core-impl/meta/stream/Stream.h" #include "playlist/PlaylistController.h" #include "playlist/PlaylistModelStack.h" using namespace Playlists; -XSPFPlaylist::XSPFPlaylist( const KUrl &url, Playlists::PlaylistProvider *provider, OnLoadAction onLoad ) +XSPFPlaylist::XSPFPlaylist( const QUrl &url, Playlists::PlaylistProvider *provider, OnLoadAction onLoad ) : PlaylistFile( url, provider ) , QDomDocument() , m_autoAppendAfterLoad( onLoad == AppendToPlaylist ) { } XSPFPlaylist::~XSPFPlaylist() { } void XSPFPlaylist::savePlaylist(QFile &file) { // if trackList item exists than no need to setup new file if ( documentElement().namedItem( "trackList" ).isNull() ) { QDomElement root = createElement( "playlist" ); root.setAttribute( "version", 1 ); root.setAttribute( "xmlns", "http://xspf.org/ns/0/" ); root.appendChild( createElement( "trackList" ) ); appendChild( root ); } setTrackList( tracks(), false ); QTextStream stream( &file ); stream.setCodec( "UTF-8" ); QDomDocument::save( stream, 2 /*indent*/, QDomNode::EncodingFromTextStream ); } bool XSPFPlaylist::processContent( QByteArray &content ) { QString errorMsg; int errorLine, errorColumn; if( !setContent( content, &errorMsg, &errorLine, &errorColumn ) ) { error() << "Error loading xml file: " "(" << errorMsg << ")" << " at line " << errorLine << ", column " << errorColumn; m_tracksLoaded = false; } else m_tracksLoaded = true; return m_tracksLoaded; } void XSPFPlaylist::load() { XSPFTrackList xspfTracks = trackList(); foreach( const XSPFTrack &track, xspfTracks ) { MetaProxy::TrackPtr proxyTrack( new MetaProxy::Track( track.location ) ); //Fill in values from xspf.. proxyTrack->setTitle( track.title ); proxyTrack->setAlbum( track.album ); proxyTrack->setArtist( track.creator ); proxyTrack->setLength( track.duration ); proxyTrack->setTrackNumber( track.trackNum ); Meta::TrackPtr metaTrack( proxyTrack.data() ); addProxyTrack( metaTrack ); } //FIXME: this needs to be moved to whatever is creating the XSPFPlaylist if( m_autoAppendAfterLoad ) The::playlistController()->insertPlaylist( ::Playlist::ModelStack::instance()->bottom()->rowCount(), Playlists::PlaylistPtr( this ) ); } bool XSPFPlaylist::loadXSPF( QTextStream &stream ) { QByteArray content = stream.readAll().toUtf8(); if ( !processContent( content ) ) return false; load(); return true; } bool XSPFPlaylist::loadXSPF( QByteArray &content ) { if ( !processContent( content ) ) return false; load(); return true; } QString XSPFPlaylist::name() const { if ( m_tracksLoaded ) return title(); else return m_url.fileName(); } QString XSPFPlaylist::title() const { return documentElement().namedItem( "title" ).firstChild().nodeValue(); } QString XSPFPlaylist::creator() const { return documentElement().namedItem( "creator" ).firstChild().nodeValue(); } QString XSPFPlaylist::annotation() const { return documentElement().namedItem( "annotation" ).firstChild().nodeValue(); } -KUrl +QUrl XSPFPlaylist::info() const { - return KUrl( documentElement().namedItem( "info" ).firstChild().nodeValue() ); + return QUrl( documentElement().namedItem( "info" ).firstChild().nodeValue() ); } -KUrl +QUrl XSPFPlaylist::location() const { - return KUrl( documentElement().namedItem( "location" ).firstChild().nodeValue() ); + return QUrl( documentElement().namedItem( "location" ).firstChild().nodeValue() ); } QString XSPFPlaylist::identifier() const { return documentElement().namedItem( "identifier" ).firstChild().nodeValue(); } -KUrl +QUrl XSPFPlaylist::image() const { - return KUrl( documentElement().namedItem( "image" ).firstChild().nodeValue() ); + return QUrl( documentElement().namedItem( "image" ).firstChild().nodeValue() ); } QDateTime XSPFPlaylist::date() const { return QDateTime::fromString( documentElement().namedItem( "date" ).firstChild().nodeValue(), Qt::ISODate ); } -KUrl +QUrl XSPFPlaylist::license() const { - return KUrl( documentElement().namedItem( "license" ).firstChild().nodeValue() ); + return QUrl( documentElement().namedItem( "license" ).firstChild().nodeValue() ); } -KUrl::List +QList XSPFPlaylist::attribution() const { const QDomNodeList nodes = documentElement().namedItem( "attribution" ).childNodes(); - KUrl::List list; + QList list; for( int i = 0, count = nodes.length(); i < count; ++i ) { const QDomNode &node = nodes.at( i ); if( !node.firstChild().nodeValue().isNull() ) list.append( node.firstChild().nodeValue() ); } return list; } -KUrl +QUrl XSPFPlaylist::link() const { - return KUrl( documentElement().namedItem( "link" ).firstChild().nodeValue() ); + return QUrl( documentElement().namedItem( "link" ).firstChild().nodeValue() ); } void XSPFPlaylist::setTitle( const QString &title ) { QDomNode titleNode = documentElement().namedItem( "title" ); if( titleNode.isNull() || !titleNode.hasChildNodes() ) { QDomNode node = createElement( "title" ); QDomNode subNode = createTextNode( title ); node.appendChild( subNode ); documentElement().insertBefore( node, documentElement().namedItem( "trackList" ) ); } else { documentElement().namedItem( "title" ).replaceChild( createTextNode( title ), documentElement().namedItem( "title" ).firstChild() ); } notifyObserversMetadataChanged(); //write changes to file directly if we know where. if( !m_url.isEmpty() ) PlaylistFile::save( false ); } void XSPFPlaylist::setCreator( const QString &creator ) { if( documentElement().namedItem( "creator" ).isNull() ) { QDomNode node = createElement( "creator" ); QDomNode subNode = createTextNode( creator ); node.appendChild( subNode ); documentElement().insertBefore( node, documentElement().namedItem( "trackList" ) ); } else { documentElement().namedItem( "creator" ).replaceChild( createTextNode( creator ), documentElement().namedItem( "creator" ).firstChild() ); } //write changes to file directly if we know where if( !m_url.isEmpty() ) PlaylistFile::save( false ); } void XSPFPlaylist::setAnnotation( const QString &annotation ) { if( documentElement().namedItem( "annotation" ).isNull() ) { QDomNode node = createElement( "annotation" ); QDomNode subNode = createTextNode( annotation ); node.appendChild( subNode ); documentElement().insertBefore( node, documentElement().namedItem( "trackList" ) ); } else { documentElement().namedItem( "annotation" ).replaceChild( createTextNode( annotation ), documentElement().namedItem( "annotation" ).firstChild() ); } //write changes to file directly if we know where. if( !m_url.isEmpty() ) PlaylistFile::save( false ); } void -XSPFPlaylist::setInfo( const KUrl &info ) +XSPFPlaylist::setInfo( const QUrl &info ) { if( documentElement().namedItem( "info" ).isNull() ) { QDomNode node = createElement( "info" ); QDomNode subNode = createTextNode( info.url() ); node.appendChild( subNode ); documentElement().insertBefore( node, documentElement().namedItem( "trackList" ) ); } else { documentElement().namedItem( "info" ).replaceChild( createTextNode( info.url() ), documentElement().namedItem( "info" ).firstChild() ); } //write changes to file directly if we know where. if( !m_url.isEmpty() ) PlaylistFile::save( false ); } void -XSPFPlaylist::setLocation( const KUrl &location ) +XSPFPlaylist::setLocation( const QUrl &location ) { if( documentElement().namedItem( "location" ).isNull() ) { QDomNode node = createElement( "location" ); QDomNode subNode = createTextNode( location.url() ); node.appendChild( subNode ); documentElement().insertBefore( node, documentElement().namedItem( "trackList" ) ); } else { documentElement().namedItem( "location" ).replaceChild( createTextNode( location.url() ), documentElement().namedItem( "location" ).firstChild() ); } //write changes to file directly if we know where. if( !m_url.isEmpty() ) PlaylistFile::save( false ); } void XSPFPlaylist::setIdentifier( const QString &identifier ) { if( documentElement().namedItem( "identifier" ).isNull() ) { QDomNode node = createElement( "identifier" ); QDomNode subNode = createTextNode( identifier ); node.appendChild( subNode ); documentElement().insertBefore( node, documentElement().namedItem( "trackList" ) ); } else { documentElement().namedItem( "identifier" ).replaceChild( createTextNode( identifier ), documentElement().namedItem( "identifier" ).firstChild() ); } //write changes to file directly if we know where. if( !m_url.isEmpty() ) PlaylistFile::save( false ); } void -XSPFPlaylist::setImage( const KUrl &image ) +XSPFPlaylist::setImage( const QUrl &image ) { if( documentElement().namedItem( "image" ).isNull() ) { QDomNode node = createElement( "image" ); QDomNode subNode = createTextNode( image.url() ); node.appendChild( subNode ); documentElement().insertBefore( node, documentElement().namedItem( "trackList" ) ); } else { documentElement().namedItem( "image" ).replaceChild( createTextNode( image.url() ), documentElement().namedItem( "image" ).firstChild() ); } //write changes to file directly if we know where. if( !m_url.isEmpty() ) PlaylistFile::save( false ); } void XSPFPlaylist::setDate( const QDateTime &date ) { /* date needs timezone info to be compliant with the standard (ex. 2005-01-08T17:10:47-05:00 ) */ if( documentElement().namedItem( "date" ).isNull() ) { QDomNode node = createElement( "date" ); QDomNode subNode = createTextNode( date.toString( "yyyy-MM-ddThh:mm:ss" ) ); node.appendChild( subNode ); documentElement().insertBefore( node, documentElement().namedItem( "trackList" ) ); } else { documentElement().namedItem( "date" ) .replaceChild( createTextNode( date.toString( "yyyy-MM-ddThh:mm:ss" ) ), documentElement().namedItem( "date" ).firstChild() ); } //write changes to file directly if we know where. if( !m_url.isEmpty() ) PlaylistFile::save( false ); } void -XSPFPlaylist::setLicense( const KUrl &license ) +XSPFPlaylist::setLicense( const QUrl &license ) { if( documentElement().namedItem( "license" ).isNull() ) { QDomNode node = createElement( "license" ); QDomNode subNode = createTextNode( license.url() ); node.appendChild( subNode ); documentElement().insertBefore( node, documentElement().namedItem( "trackList" ) ); } else { documentElement().namedItem( "license" ).replaceChild( createTextNode( license.url() ), documentElement().namedItem( "license" ).firstChild() ); } //write changes to file directly if we know where. if( !m_url.isEmpty() ) PlaylistFile::save( false ); } void -XSPFPlaylist::setAttribution( const KUrl &attribution, bool append ) +XSPFPlaylist::setAttribution( const QUrl &attribution, bool append ) { if( !attribution.isValid() ) return; if( documentElement().namedItem( "attribution" ).isNull() ) { documentElement().insertBefore( createElement( "attribution" ), documentElement().namedItem( "trackList" ) ); } if( append ) { QDomNode subNode = createElement( "location" ); QDomNode subSubNode = createTextNode( attribution.url() ); subNode.appendChild( subSubNode ); QDomNode first = documentElement().namedItem( "attribution" ).firstChild(); documentElement().namedItem( "attribution" ).insertBefore( subNode, first ); } else { QDomNode node = createElement( "attribution" ); QDomNode subNode = createElement( "location" ); QDomNode subSubNode = createTextNode( attribution.url() ); subNode.appendChild( subSubNode ); node.appendChild( subNode ); documentElement().replaceChild( node, documentElement().namedItem( "attribution" ) ); } //write changes to file directly if we know where. if( !m_url.isEmpty() ) PlaylistFile::save( false ); } void -XSPFPlaylist::setLink( const KUrl &link ) +XSPFPlaylist::setLink( const QUrl &link ) { if( documentElement().namedItem( "link" ).isNull() ) { QDomNode node = createElement( "link" ); QDomNode subNode = createTextNode( link.url() ); node.appendChild( subNode ); documentElement().insertBefore( node, documentElement().namedItem( "trackList" ) ); } else { documentElement().namedItem( "link" ).replaceChild( createTextNode( link.url() ), documentElement().namedItem( "link" ).firstChild() ); } //write changes to file directly if we know where. if( !m_url.isEmpty() ) PlaylistFile::save( false ); } XSPFTrackList XSPFPlaylist::trackList() { XSPFTrackList list; QDomNode trackList = documentElement().namedItem( "trackList" ); QDomNode subNode = trackList.firstChild(); QDomNode subSubNode; while( !subNode.isNull() ) { XSPFTrack track; subSubNode = subNode.firstChild(); if( subNode.nodeName() == "track" ) { while( !subSubNode.isNull() ) { if( subSubNode.nodeName() == "location" ) { QByteArray path = subSubNode.firstChild().nodeValue().toAscii(); path.replace( '\\', '/' ); - KUrl url = getAbsolutePath( KUrl::fromEncoded( path ) ); + QUrl url = getAbsolutePath( QUrl::fromEncoded( path ) ); track.location = url; } else if( subSubNode.nodeName() == "title" ) track.title = subSubNode.firstChild().nodeValue(); else if( subSubNode.nodeName() == "creator" ) track.creator = subSubNode.firstChild().nodeValue(); else if( subSubNode.nodeName() == "duration" ) track.duration = subSubNode.firstChild().nodeValue().toInt(); else if( subSubNode.nodeName() == "annotation" ) track.annotation = subSubNode.firstChild().nodeValue(); else if( subSubNode.nodeName() == "album" ) track.album = subSubNode.firstChild().nodeValue(); else if( subSubNode.nodeName() == "trackNum" ) track.trackNum = (uint)subSubNode.firstChild().nodeValue().toInt(); else if( subSubNode.nodeName() == "identifier" ) track.identifier = subSubNode.firstChild().nodeValue(); else if( subSubNode.nodeName() == "info" ) track.info = subSubNode.firstChild().nodeValue(); else if( subSubNode.nodeName() == "image" ) track.image = subSubNode.firstChild().nodeValue(); else if( subSubNode.nodeName() == "link" ) track.link = subSubNode.firstChild().nodeValue(); subSubNode = subSubNode.nextSibling(); } } list.append( track ); subNode = subNode.nextSibling(); } return list; } void XSPFPlaylist::setTrackList( Meta::TrackList trackList, bool append ) { //documentation of attributes from http://www.xspf.org/xspf-v1.html if( documentElement().namedItem( "trackList" ).isNull() ) documentElement().appendChild( createElement( "trackList" ) ); QDomNode node = createElement( "trackList" ); Meta::TrackPtr track; foreach( track, trackList ) // krazy:exclude=foreach { QDomNode subNode = createElement( "track" ); //URI of resource to be rendered. QDomNode location = createElement( "location" ); //Human-readable name of the track that authored the resource QDomNode title = createElement( "title" ); //Human-readable name of the entity that authored the resource. QDomNode creator = createElement( "creator" ); //A human-readable comment on the track. QDomNode annotation = createElement( "annotation" ); //Human-readable name of the collection from which the resource comes QDomNode album = createElement( "album" ); //Integer > 0 giving the ordinal position of the media in the album. QDomNode trackNum = createElement( "trackNum" ); //The time to render a resource, in milliseconds. It MUST be a nonNegativeInteger. QDomNode duration = createElement( "duration" ); //location-independent name, such as a MusicBrainz identifier. MUST be a legal URI. QDomNode identifier = createElement( "identifier" ); //info - URI of a place where this resource can be bought or more info can be found. //QDomNode info = createElement( "info" ); //image - URI of an image to display for the duration of the track. //QDomNode image = createElement( "image" ); //link - element allows XSPF to be extended without the use of XML namespaces. //QDomNode link = createElement( "link" ); //QDomNode meta //amarok specific queue info, see the XSPF specification's meta element QDomElement queue = createElement( "meta" ); queue.setAttribute( "rel", "http://amarok.kde.org/queue" ); //QDomNode extension #define APPENDNODE( X, Y ) \ { \ X.appendChild( createTextNode( Y ) ); \ subNode.appendChild( X ); \ } APPENDNODE( location, trackLocation( track ) ) APPENDNODE( identifier, track->uidUrl() ) Capabilities::StreamInfoCapability *streamInfo = track->create(); if( streamInfo ) // We have a stream, use it's metadata instead of the tracks. { if( !streamInfo->streamName().isEmpty() ) APPENDNODE( title, streamInfo->streamName() ) if( !streamInfo->streamSource().isEmpty() ) APPENDNODE( creator, streamInfo->streamSource() ) delete streamInfo; } else { if( !track->name().isEmpty() ) APPENDNODE(title, track->name() ) if( track->artist() && !track->artist()->name().isEmpty() ) APPENDNODE(creator, track->artist()->name() ); } if( !track->comment().isEmpty() ) APPENDNODE(annotation, track->comment() ); if( track->album() && !track->album()->name().isEmpty() ) APPENDNODE( album, track->album()->name() ); if( track->trackNumber() > 0 ) APPENDNODE( trackNum, QString::number( track->trackNumber() ) ); if( track->length() > 0 ) APPENDNODE( duration, QString::number( track->length() ) ); node.appendChild( subNode ); } #undef APPENDNODE if( append ) { while( !node.isNull() ) { documentElement().namedItem( "trackList" ).appendChild( node.firstChild() ); node = node.nextSibling(); } } else documentElement().replaceChild( node, documentElement().namedItem( "trackList" ) ); } void XSPFPlaylist::setQueue( const QList &queue ) { QDomElement q = createElement( "queue" ); foreach( int row, queue ) { QDomElement qTrack = createElement( "track" ); qTrack.appendChild( createTextNode( QString::number( row ) ) ); q.appendChild( qTrack ); } QDomElement extension = createElement( "extension" ); extension.setAttribute( "application", "http://amarok.kde.org" ); extension.appendChild( q ); QDomNode root = firstChild(); root.appendChild( extension ); } QList XSPFPlaylist::queue() { QList tracks; QDomElement extension = documentElement().firstChildElement( "extension" ); if( extension.isNull() ) return tracks; if( extension.attribute( "application" ) != "http://amarok.kde.org" ) return tracks; QDomElement queue = extension.firstChildElement( "queue" ); if( queue.isNull() ) return tracks; for( QDomElement trackElem = queue.firstChildElement( "track" ); !trackElem.isNull(); trackElem = trackElem.nextSiblingElement( "track" ) ) { tracks << trackElem.text().toInt(); } return tracks; } void XSPFPlaylist::setName( const QString &name ) { PlaylistFile::setName( name ); setTitle( name ); } diff --git a/src/core-impl/playlists/types/file/xspf/XSPFPlaylist.h b/src/core-impl/playlists/types/file/xspf/XSPFPlaylist.h index 610143e434..c48d74ed08 100644 --- a/src/core-impl/playlists/types/file/xspf/XSPFPlaylist.h +++ b/src/core-impl/playlists/types/file/xspf/XSPFPlaylist.h @@ -1,136 +1,136 @@ /**************************************************************************************** * Copyright (c) 2007 Bart Cerneels * * Copyright (c) 2006 Mattias Fliesberg * * * * 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, see . * ****************************************************************************************/ #ifndef XSPFPLAYLIST_H #define XSPFPLAYLIST_H #include "core-impl/playlists/types/file/PlaylistFile.h" #include #include namespace Playlists { /* convenience struct for internal use */ struct XSPFTrack { // initialize primitive types, don't give stochasticity a chance! XSPFTrack() : trackNum( 0 ), duration( 0 ) {} - KUrl location; + QUrl location; QString identifier; QString title; QString creator; QString annotation; - KUrl info; - KUrl image; + QUrl info; + QUrl image; QString album; uint trackNum; uint duration; - KUrl link; + QUrl link; }; typedef QList XSPFTrackList; /** * @author Bart Cerneels */ class AMAROK_EXPORT XSPFPlaylist : public PlaylistFile, public QDomDocument { public: enum OnLoadAction { NoAction, // do nothing on playlist load AppendToPlaylist, // apped this playlist to play queue on load }; /** * Creates a new XSPFPlaylist * * @param url The Url of the xspf file to load. * @param onLoad Should this playlist automatically append itself to the playlist when loaded (useful when loading a remote url as it * allows the caller to do it in a "one shot" way and not have to worry about waiting untill download and parsing is completed. */ - explicit XSPFPlaylist( const KUrl &url, PlaylistProvider *provider = 0, OnLoadAction onLoad = NoAction ); + explicit XSPFPlaylist( const QUrl &url, PlaylistProvider *provider = 0, OnLoadAction onLoad = NoAction ); ~XSPFPlaylist(); virtual QString name() const; /* convenience functions */ QString title() const; QString creator() const; QString annotation() const; - KUrl info() const; - KUrl location() const; + QUrl info() const; + QUrl location() const; QString identifier() const; - KUrl image() const; + QUrl image() const; QDateTime date() const; - KUrl license() const; - KUrl::List attribution() const; - KUrl link() const; + QUrl license() const; + QList attribution() const; + QUrl link() const; /* Extra XSPF setter methods: */ void setTitle( const QString &title ); void setCreator( const QString &creator ); void setAnnotation( const QString &annotation ); - void setInfo( const KUrl &info ); - void setLocation( const KUrl &location ); + void setInfo( const QUrl &info ); + void setLocation( const QUrl &location ); void setIdentifier( const QString &identifier ); - void setImage( const KUrl &image ); + void setImage( const QUrl &image ); void setDate( const QDateTime &date ); - void setLicense( const KUrl &license ); - void setAttribution( const KUrl &attribution, bool append = true ); - void setLink( const KUrl &link ); + void setLicense( const QUrl &license ); + void setAttribution( const QUrl &attribution, bool append = true ); + void setLink( const QUrl &link ); void setTrackList( Meta::TrackList trackList, bool append = false ); /* PlaylistFile methods */ virtual bool load( QTextStream &stream ) { return loadXSPF( stream ); } virtual bool load( QByteArray &content ) { return loadXSPF( content ); } /* Overrides filename and title */ void setName(const QString &name); virtual QString extension() const { return "xspf"; } virtual QString mimetype() const { return "application/xspf+xml"; } virtual bool save( bool relative ) { return PlaylistFile::save( relative ); } void setQueue( const QList &queue ); QList queue(); protected: virtual void savePlaylist( QFile &file ); private: XSPFTrackList trackList(); /** * Load file after content was set */ void load(); /** * Sets content in terms of xml document * @return true is xml-document is correct, false overwise */ bool processContent( QByteArray &content ); bool loadXSPF( QTextStream &stream ); bool loadXSPF( QByteArray &content ); bool m_autoAppendAfterLoad; }; } #endif diff --git a/src/core-impl/podcasts/sql/PodcastSettingsDialog.cpp b/src/core-impl/podcasts/sql/PodcastSettingsDialog.cpp index d144989f81..4424f25493 100644 --- a/src/core-impl/podcasts/sql/PodcastSettingsDialog.cpp +++ b/src/core-impl/podcasts/sql/PodcastSettingsDialog.cpp @@ -1,163 +1,163 @@ /**************************************************************************************** * Copyright (c) 2006-2008 Bart Cerneels * * * * 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, see . * ****************************************************************************************/ #include "PodcastSettingsDialog.h" #include "ui_PodcastSettingsBase.h" #include "PodcastFilenameLayoutConfigDialog.h" #include "core/support/Debug.h" #include #include #include PodcastSettingsDialog::PodcastSettingsDialog( Podcasts::SqlPodcastChannelPtr channel, QWidget* parent ) : KDialog( parent ) , m_ps( new Ui::PodcastSettingsBase() ) , m_channel( channel ) { QWidget* main = new QWidget( this ); m_ps->setupUi( main ); setMainWidget( main ); setCaption( i18nc("change options", "Configure %1", m_channel->title() ) ); setModal( true ); setButtons( Apply | Cancel | Ok ); setDefaultButton( Ok ); showButtonSeparator( true ); init(); } void PodcastSettingsDialog::init() { QString url = m_channel->url().url(); m_ps->m_urlLineEdit->setText( url ); m_ps->m_saveLocation->setMode( KFile::Directory | KFile::ExistingOnly ); m_ps->m_saveLocation->setUrl( m_channel->saveLocation() ); m_ps->m_autoFetchCheck->setChecked( m_channel->autoScan() ); if( m_channel->fetchType() == Podcasts::PodcastChannel::StreamOrDownloadOnDemand ) { m_ps->m_streamRadio->setChecked( true ); m_ps->m_downloadRadio->setChecked( false ); } else if( m_channel->fetchType() == Podcasts::PodcastChannel::DownloadWhenAvailable ) { m_ps->m_streamRadio->setChecked( false ); m_ps->m_downloadRadio->setChecked( true ); } m_ps->m_purgeCheck->setChecked( m_channel->hasPurge() ); m_ps->m_purgeCountSpinBox->setValue( m_channel->purgeCount() ); m_ps->m_purgeCountSpinBox->setSuffix( ki18np( " Item", " Items" ) ); if( !m_channel->hasPurge() ) { m_ps->m_purgeCountSpinBox->setEnabled( false ); m_ps->m_purgeCountLabel->setEnabled( false ); } m_ps->m_writeTagsCheck->setChecked( m_channel->writeTags() ); enableButtonApply( false ); // Connects for modification check connect( m_ps->m_urlLineEdit, SIGNAL(textChanged(QString)), SLOT(checkModified()) ); connect( m_ps->m_saveLocation, SIGNAL(textChanged(QString)), SLOT(checkModified()) ); connect( m_ps->m_autoFetchCheck, SIGNAL(clicked()), SLOT(checkModified()) ); connect( m_ps->m_streamRadio, SIGNAL(clicked()), SLOT(checkModified()) ); connect( m_ps->m_downloadRadio, SIGNAL(clicked()), SLOT(checkModified()) ); connect( m_ps->m_purgeCheck, SIGNAL(clicked()), SLOT(checkModified()) ); connect( m_ps->m_purgeCountSpinBox, SIGNAL(valueChanged(int)), SLOT(checkModified()) ); connect( m_ps->m_writeTagsCheck, SIGNAL(clicked()), SLOT(checkModified()) ); connect( m_ps->m_filenameLayoutConfigWidgetButton, SIGNAL(clicked()), SLOT(launchFilenameLayoutConfigDialog()) ); connect( this, SIGNAL(applyClicked()), this ,SLOT(slotApply()) ); connect( this, SIGNAL(okClicked()), this, SLOT(slotApply()) ); } void PodcastSettingsDialog::slotFeedUrlClicked( const QString &url ) //SLOT { //adding url to clipboard for users convenience QApplication::clipboard()->setText( url ); } bool PodcastSettingsDialog::hasChanged() { bool fetchTypeChanged = true; if( ( m_ps->m_streamRadio->isChecked() && m_channel->fetchType() == Podcasts::PodcastChannel::StreamOrDownloadOnDemand ) || ( m_ps->m_downloadRadio->isChecked() && m_channel->fetchType() == Podcasts::PodcastChannel::DownloadWhenAvailable ) ) { fetchTypeChanged = false; } return( m_channel->url() != m_ps->m_urlLineEdit->text() || m_channel->saveLocation() != m_ps->m_saveLocation->url() || m_channel->autoScan() != m_ps->m_autoFetchCheck->isChecked() || m_channel->hasPurge() != m_ps->m_purgeCheck->isChecked() || m_channel->purgeCount() != m_ps->m_purgeCountSpinBox->value() || fetchTypeChanged || m_channel->writeTags() != m_ps->m_writeTagsCheck->isChecked() ); } void PodcastSettingsDialog::checkModified() //slot { enableButtonApply( hasChanged() ); } void PodcastSettingsDialog::slotApply() //slot { - m_channel->setUrl( KUrl( m_ps->m_urlLineEdit->text() ) ); + m_channel->setUrl( QUrl( m_ps->m_urlLineEdit->text() ) ); m_channel->setAutoScan( m_ps->m_autoFetchCheck->isChecked() ); m_channel->setFetchType( m_ps->m_downloadRadio->isChecked() ? Podcasts::PodcastChannel::DownloadWhenAvailable : Podcasts::PodcastChannel::StreamOrDownloadOnDemand ); m_channel->setSaveLocation( m_ps->m_saveLocation->url() ); m_channel->setPurge( m_ps->m_purgeCheck->isChecked() ); m_channel->setPurgeCount( m_ps->m_purgeCountSpinBox->value() ); m_channel->setWriteTags( m_ps->m_writeTagsCheck->isChecked() ); enableButtonApply( false ); } bool PodcastSettingsDialog::configure() { return exec() == QDialog::Accepted; } void PodcastSettingsDialog::launchFilenameLayoutConfigDialog() { PodcastFilenameLayoutConfigDialog pflcDialog( m_channel, this ); pflcDialog.configure(); } diff --git a/src/core-impl/podcasts/sql/SqlPodcastMeta.cpp b/src/core-impl/podcasts/sql/SqlPodcastMeta.cpp index 14813d3f82..254d9379cb 100644 --- a/src/core-impl/podcasts/sql/SqlPodcastMeta.cpp +++ b/src/core-impl/podcasts/sql/SqlPodcastMeta.cpp @@ -1,949 +1,949 @@ /**************************************************************************************** * Copyright (c) 2008 Bart Cerneels * * * * 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, see . * ****************************************************************************************/ #include "SqlPodcastMeta.h" #include "amarokurls/BookmarkMetaActions.h" #include "amarokurls/PlayUrlRunner.h" #include "core/capabilities/ActionsCapability.h" #include #include "core/meta/TrackEditor.h" #include "core/support/Debug.h" #include "core-impl/capabilities/timecode/TimecodeLoadCapability.h" #include "core-impl/capabilities/timecode/TimecodeWriteCapability.h" #include "core-impl/collections/support/CollectionManager.h" #include "core-impl/storage/StorageManager.h" #include "core-impl/meta/proxy/MetaProxy.h" #include "core-impl/meta/file/FileTrackProvider.h" #include "core-impl/podcasts/sql/SqlPodcastProvider.h" #include #include using namespace Podcasts; static FileTrackProvider myFileTrackProvider; // we need it to be available for lookups class TimecodeWriteCapabilityPodcastImpl : public Capabilities::TimecodeWriteCapability { public: TimecodeWriteCapabilityPodcastImpl( Podcasts::PodcastEpisode *episode ) : Capabilities::TimecodeWriteCapability() , m_episode( episode ) {} virtual bool writeTimecode ( qint64 miliseconds ) { DEBUG_BLOCK return Capabilities::TimecodeWriteCapability::writeTimecode( miliseconds, Meta::TrackPtr::dynamicCast( m_episode ) ); } virtual bool writeAutoTimecode ( qint64 miliseconds ) { DEBUG_BLOCK return Capabilities::TimecodeWriteCapability::writeAutoTimecode( miliseconds, Meta::TrackPtr::dynamicCast( m_episode ) ); } private: Podcasts::PodcastEpisodePtr m_episode; }; class TimecodeLoadCapabilityPodcastImpl : public Capabilities::TimecodeLoadCapability { public: TimecodeLoadCapabilityPodcastImpl( Podcasts::PodcastEpisode *episode ) : Capabilities::TimecodeLoadCapability() , m_episode( episode ) { DEBUG_BLOCK debug() << "episode: " << m_episode->name(); } virtual bool hasTimecodes() { if ( loadTimecodes().size() > 0 ) return true; return false; } virtual BookmarkList loadTimecodes() { DEBUG_BLOCK if ( m_episode && m_episode->playableUrl().isValid() ) { BookmarkList list = PlayUrlRunner::bookmarksFromUrl( m_episode->playableUrl() ); return list; } else return BookmarkList(); } private: Podcasts::PodcastEpisodePtr m_episode; }; Meta::TrackList SqlPodcastEpisode::toTrackList( Podcasts::SqlPodcastEpisodeList episodes ) { Meta::TrackList tracks; foreach( SqlPodcastEpisodePtr sqlEpisode, episodes ) tracks << Meta::TrackPtr::dynamicCast( sqlEpisode ); return tracks; } Podcasts::PodcastEpisodeList SqlPodcastEpisode::toPodcastEpisodeList( SqlPodcastEpisodeList episodes ) { Podcasts::PodcastEpisodeList sqlEpisodes; foreach( SqlPodcastEpisodePtr sqlEpisode, episodes ) sqlEpisodes << Podcasts::PodcastEpisodePtr::dynamicCast( sqlEpisode ); return sqlEpisodes; } SqlPodcastEpisode::SqlPodcastEpisode( const QStringList &result, SqlPodcastChannelPtr sqlChannel ) : Podcasts::PodcastEpisode( Podcasts::PodcastChannelPtr::staticCast( sqlChannel ) ) , m_channel( sqlChannel ) { SqlStorage *sqlStorage = StorageManager::instance()->sqlStorage(); QStringList::ConstIterator iter = result.constBegin(); m_dbId = (*(iter++)).toInt(); - m_url = KUrl( *(iter++) ); + m_url = QUrl( *(iter++) ); int channelId = (*(iter++)).toInt(); Q_UNUSED( channelId ); - m_localUrl = KUrl( *(iter++) ); + m_localUrl = QUrl( *(iter++) ); m_guid = *(iter++); m_title = *(iter++); m_subtitle = *(iter++); m_sequenceNumber = (*(iter++)).toInt(); m_description = *(iter++); m_mimeType = *(iter++); m_pubDate = QDateTime::fromString( *(iter++), Qt::ISODate ); m_duration = (*(iter++)).toInt(); m_fileSize = (*(iter++)).toInt(); m_isNew = sqlStorage->boolTrue() == (*(iter++)); m_isKeep = sqlStorage->boolTrue() == (*(iter++)); Q_ASSERT_X( iter == result.constEnd(), "SqlPodcastEpisode( PodcastCollection*, QStringList )", "number of expected fields did not match number of actual fields" ); setupLocalFile(); } //TODO: why do PodcastMetaCommon and PodcastEpisode not have an appropriate copy constructor? SqlPodcastEpisode::SqlPodcastEpisode( Podcasts::PodcastEpisodePtr episode ) : Podcasts::PodcastEpisode() , m_dbId( 0 ) , m_isKeep( false ) { m_channel = SqlPodcastChannelPtr::dynamicCast( episode->channel() ); if( !m_channel && episode->channel() ) { debug() << "BUG: creating SqlEpisode but not an sqlChannel!!!"; debug() << episode->channel()->title(); debug() << m_channel->title(); } // PodcastMetaCommon m_title = episode->title(); m_description = episode->description(); m_keywords = episode->keywords(); m_subtitle = episode->subtitle(); m_summary = episode->summary(); m_author = episode->author(); // PodcastEpisode m_guid = episode->guid(); - m_url = KUrl( episode->uidUrl() ); + m_url = QUrl( episode->uidUrl() ); m_localUrl = episode->localUrl(); m_mimeType = episode->mimeType(); m_pubDate = episode->pubDate(); m_duration = episode->duration(); m_fileSize = episode->filesize(); m_sequenceNumber = episode->sequenceNumber(); m_isNew = episode->isNew(); // The album, artist, composer, genre and year fields // contain proxy objects with internal references to this. // These proxies are created by Podcasts::PodcastEpisode(), so // these fields don't have to be set here. //commit to the database updateInDb(); setupLocalFile(); } SqlPodcastEpisode::SqlPodcastEpisode( PodcastChannelPtr channel, Podcasts::PodcastEpisodePtr episode ) : Podcasts::PodcastEpisode() , m_dbId( 0 ) , m_isKeep( false ) { m_channel = SqlPodcastChannelPtr::dynamicCast( channel ); if( !m_channel && episode->channel() ) { debug() << "BUG: creating SqlEpisode but not an sqlChannel!!!"; debug() << episode->channel()->title(); debug() << m_channel->title(); } // PodcastMetaCommon m_title = episode->title(); m_description = episode->description(); m_keywords = episode->keywords(); m_subtitle = episode->subtitle(); m_summary = episode->summary(); m_author = episode->author(); // PodcastEpisode m_guid = episode->guid(); - m_url = KUrl( episode->uidUrl() ); + m_url = QUrl( episode->uidUrl() ); m_localUrl = episode->localUrl(); m_mimeType = episode->mimeType(); m_pubDate = episode->pubDate(); m_duration = episode->duration(); m_fileSize = episode->filesize(); m_sequenceNumber = episode->sequenceNumber(); m_isNew = episode->isNew(); // The album, artist, composer, genre and year fields // contain proxy objects with internal references to this. // These proxies are created by Podcasts::PodcastEpisode(), so // these fields don't have to be set here. //commit to the database updateInDb(); setupLocalFile(); } void SqlPodcastEpisode::setupLocalFile() { if( m_localUrl.isEmpty() || !QFileInfo( m_localUrl.toLocalFile() ).exists() ) return; MetaProxy::TrackPtr proxyTrack( new MetaProxy::Track( m_localUrl, MetaProxy::Track::ManualLookup ) ); m_localFile = Meta::TrackPtr( proxyTrack.data() ); // avoid static_cast /* following won't write to actual file, because MetaProxy::Track hasn't yet looked * up the underlying track. It will just set some cached values. */ writeTagsToFile(); proxyTrack->lookupTrack( &myFileTrackProvider ); } SqlPodcastEpisode::~SqlPodcastEpisode() { } void SqlPodcastEpisode::setNew( bool isNew ) { PodcastEpisode::setNew( isNew ); updateInDb(); } void SqlPodcastEpisode::setKeep( bool isKeep ) { m_isKeep = isKeep; updateInDb(); } void -SqlPodcastEpisode::setLocalUrl( const KUrl &url ) +SqlPodcastEpisode::setLocalUrl( const QUrl &url ) { m_localUrl = url; updateInDb(); if( m_localUrl.isEmpty() && !m_localFile.isNull() ) { m_localFile.clear(); notifyObservers(); } else { //if we had a local file previously it should get deleted by the KSharedPtr. m_localFile = new MetaFile::Track( m_localUrl ); if( m_channel->writeTags() ) writeTagsToFile(); } } qint64 SqlPodcastEpisode::length() const { //if downloaded get the duration from the file, else use the value read from the feed if( m_localFile.isNull() ) return m_duration * 1000; return m_localFile->length(); } bool SqlPodcastEpisode::hasCapabilityInterface( Capabilities::Capability::Type type ) const { switch( type ) { case Capabilities::Capability::Actions: case Capabilities::Capability::WriteTimecode: case Capabilities::Capability::LoadTimecode: //only downloaded episodes can be position marked // return !localUrl().isEmpty(); return true; default: return false; } } Capabilities::Capability* SqlPodcastEpisode::createCapabilityInterface( Capabilities::Capability::Type type ) { switch( type ) { case Capabilities::Capability::Actions: { QList< QAction * > actions; actions << new BookmarkCurrentTrackPositionAction( 0 ); return new Capabilities::ActionsCapability( actions ); } case Capabilities::Capability::WriteTimecode: return new TimecodeWriteCapabilityPodcastImpl( this ); case Capabilities::Capability::LoadTimecode: return new TimecodeLoadCapabilityPodcastImpl( this ); default: return 0; } } void SqlPodcastEpisode::finishedPlaying( double playedFraction ) { if( length() <= 0 || playedFraction >= 0.1 ) setNew( false ); PodcastEpisode::finishedPlaying( playedFraction ); } QString SqlPodcastEpisode::name() const { if( m_localFile.isNull() ) return m_title; return m_localFile->name(); } QString SqlPodcastEpisode::prettyName() const { /*for now just do the same as name, but in the future we might want to used a cleaned up string using some sort of regex tag rewrite for podcasts. decapitateString on steroides. */ return name(); } void SqlPodcastEpisode::setTitle( const QString &title ) { m_title = title; Meta::TrackEditorPtr ec = m_localFile ? m_localFile->editor() : Meta::TrackEditorPtr(); if( ec ) ec->setTitle( title ); } Meta::ArtistPtr SqlPodcastEpisode::artist() const { if( m_localFile.isNull() ) return m_artistPtr; return m_localFile->artist(); } Meta::ComposerPtr SqlPodcastEpisode::composer() const { if( m_localFile.isNull() ) return m_composerPtr; return m_localFile->composer(); } Meta::GenrePtr SqlPodcastEpisode::genre() const { if( m_localFile.isNull() ) return m_genrePtr; return m_localFile->genre(); } Meta::YearPtr SqlPodcastEpisode::year() const { if( m_localFile.isNull() ) return m_yearPtr; return m_localFile->year(); } Meta::TrackEditorPtr SqlPodcastEpisode::editor() { if( m_localFile ) return m_localFile->editor(); else return Meta::TrackEditorPtr(); } bool SqlPodcastEpisode::writeTagsToFile() { if( !m_localFile ) return false; Meta::TrackEditorPtr ec = m_localFile->editor(); if( !ec ) return false; debug() << "writing tags for podcast episode " << title() << "to " << m_localUrl.url(); ec->beginUpdate(); ec->setTitle( m_title ); ec->setAlbum( m_channel->title() ); ec->setArtist( m_channel->author() ); ec->setGenre( i18n( "Podcast" ) ); ec->setYear( m_pubDate.date().year() ); ec->endUpdate(); notifyObservers(); return true; } void SqlPodcastEpisode::updateInDb() { SqlStorage *sqlStorage = StorageManager::instance()->sqlStorage(); QString boolTrue = sqlStorage->boolTrue(); QString boolFalse = sqlStorage->boolFalse(); #define escape(x) sqlStorage->escape(x) QString command; QTextStream stream( &command ); if( m_dbId ) { stream << "UPDATE podcastepisodes "; stream << "SET url='"; stream << escape(m_url.url()); stream << "', channel="; stream << m_channel->dbId(); stream << ", localurl='"; stream << escape(m_localUrl.url()); stream << "', guid='"; stream << escape(m_guid); stream << "', title='"; stream << escape(m_title); stream << "', subtitle='"; stream << escape(m_subtitle); stream << "', sequencenumber="; stream << m_sequenceNumber; stream << ", description='"; stream << escape(m_description); stream << "', mimetype='"; stream << escape(m_mimeType); stream << "', pubdate='"; stream << escape(m_pubDate.toString(Qt::ISODate)); stream << "', duration="; stream << m_duration; stream << ", filesize="; stream << m_fileSize; stream << ", isnew="; stream << (isNew() ? boolTrue : boolFalse); stream << ", iskeep="; stream << (isKeep() ? boolTrue : boolFalse); stream << " WHERE id="; stream << m_dbId; stream << ";"; sqlStorage->query( command ); } else { stream << "INSERT INTO podcastepisodes ("; stream << "url,channel,localurl,guid,title,subtitle,sequencenumber,description,"; stream << "mimetype,pubdate,duration,filesize,isnew,iskeep) "; stream << "VALUES ( '"; stream << escape(m_url.url()) << "', "; stream << m_channel->dbId() << ", '"; stream << escape(m_localUrl.url()) << "', '"; stream << escape(m_guid) << "', '"; stream << escape(m_title) << "', '"; stream << escape(m_subtitle) << "', "; stream << m_sequenceNumber << ", '"; stream << escape(m_description) << "', '"; stream << escape(m_mimeType) << "', '"; stream << escape(m_pubDate.toString(Qt::ISODate)) << "', "; stream << m_duration << ", "; stream << m_fileSize << ", "; stream << (isNew() ? boolTrue : boolFalse) << ", "; stream << (isKeep() ? boolTrue : boolFalse); stream << ");"; m_dbId = sqlStorage->insert( command, "podcastepisodes" ); } } void SqlPodcastEpisode::deleteFromDb() { SqlStorage *sqlStorage = StorageManager::instance()->sqlStorage(); sqlStorage->query( QString( "DELETE FROM podcastepisodes WHERE id = %1;" ).arg( dbId() ) ); } Playlists::PlaylistPtr SqlPodcastChannel::toPlaylistPtr( SqlPodcastChannelPtr sqlChannel ) { Playlists::PlaylistPtr playlist = Playlists::PlaylistPtr::dynamicCast( sqlChannel ); return playlist; } SqlPodcastChannelPtr SqlPodcastChannel::fromPlaylistPtr( Playlists::PlaylistPtr playlist ) { SqlPodcastChannelPtr sqlChannel = SqlPodcastChannelPtr::dynamicCast( playlist ); return sqlChannel; } SqlPodcastChannel::SqlPodcastChannel( SqlPodcastProvider *provider, const QStringList &result ) : Podcasts::PodcastChannel() , m_episodesLoaded( false ) , m_trackCacheIsValid( false ) , m_provider( provider ) { SqlStorage *sqlStorage = StorageManager::instance()->sqlStorage(); QStringList::ConstIterator iter = result.constBegin(); m_dbId = (*(iter++)).toInt(); - m_url = KUrl( *(iter++) ); + m_url = QUrl( *(iter++) ); m_title = *(iter++); m_webLink = *(iter++); m_imageUrl = *(iter++); m_description = *(iter++); m_copyright = *(iter++); - m_directory = KUrl( *(iter++) ); + m_directory = QUrl( *(iter++) ); m_labels = QStringList( QString( *(iter++) ).split( ',', QString::SkipEmptyParts ) ); m_subscribeDate = QDate::fromString( *(iter++) ); m_autoScan = sqlStorage->boolTrue() == *(iter++); m_fetchType = (*(iter++)).toInt() == DownloadWhenAvailable ? DownloadWhenAvailable : StreamOrDownloadOnDemand; m_purge = sqlStorage->boolTrue() == *(iter++); m_purgeCount = (*(iter++)).toInt(); m_writeTags = sqlStorage->boolTrue() == *(iter++); m_filenameLayout = *(iter++); } SqlPodcastChannel::SqlPodcastChannel( Podcasts::SqlPodcastProvider *provider, Podcasts::PodcastChannelPtr channel ) : Podcasts::PodcastChannel() , m_dbId( 0 ) , m_trackCacheIsValid( false ) , m_provider( provider ) , m_filenameLayout( "%default%" ) { // PodcastMetaCommon m_title = channel->title(); m_description = channel->description(); m_keywords = channel->keywords(); m_subtitle = channel->subtitle(); m_summary = channel->summary(); m_author = channel->author(); // PodcastChannel m_url = channel->url(); m_webLink = channel->webLink(); m_imageUrl = channel->imageUrl(); m_labels = channel->labels(); m_subscribeDate = channel->subscribeDate(); m_copyright = channel->copyright(); if( channel->hasImage() ) m_image = channel->image(); //Default Settings - m_directory = KUrl( m_provider->baseDownloadDir() ); + m_directory = QUrl( m_provider->baseDownloadDir() ); m_directory.addPath( Amarok::vfatPath( m_title ) ); m_autoScan = true; m_fetchType = StreamOrDownloadOnDemand; m_purge = false; m_purgeCount = 10; m_writeTags = true; updateInDb(); foreach( Podcasts::PodcastEpisodePtr episode, channel->episodes() ) { episode->setChannel( PodcastChannelPtr( this ) ); SqlPodcastEpisode *sqlEpisode = new SqlPodcastEpisode( episode ); m_episodes << SqlPodcastEpisodePtr( sqlEpisode ); } m_episodesLoaded = true; } int SqlPodcastChannel::trackCount() const { if( m_episodesLoaded ) return m_episodes.count(); else return -1; } void SqlPodcastChannel::triggerTrackLoad() { if( !m_episodesLoaded ) loadEpisodes(); notifyObserversTracksLoaded(); } Playlists::PlaylistProvider * SqlPodcastChannel::provider() const { return dynamic_cast( m_provider ); } QStringList SqlPodcastChannel::groups() { return m_labels; } void SqlPodcastChannel::setGroups( const QStringList &groups ) { m_labels = groups; } -KUrl +QUrl SqlPodcastChannel::uidUrl() const { - return KUrl( QString( "amarok-sqlpodcastuid://%1").arg( m_dbId ) ); + return QUrl( QString( "amarok-sqlpodcastuid://%1").arg( m_dbId ) ); } SqlPodcastChannel::~SqlPodcastChannel() { m_episodes.clear(); } void SqlPodcastChannel::setTitle( const QString &title ) { /* also change the savelocation if a title is not set yet. This is a special condition that can happen when first fetching a podcast feed */ if( m_title.isEmpty() ) m_directory.addPath( Amarok::vfatPath( title ) ); m_title = title; } Podcasts::PodcastEpisodeList SqlPodcastChannel::episodes() const { return SqlPodcastEpisode::toPodcastEpisodeList( m_episodes ); } void SqlPodcastChannel::setImage( const QImage &image ) { DEBUG_BLOCK m_image = image; } void -SqlPodcastChannel::setImageUrl( const KUrl &imageUrl ) +SqlPodcastChannel::setImageUrl( const QUrl &imageUrl ) { DEBUG_BLOCK debug() << imageUrl; m_imageUrl = imageUrl; if( imageUrl.isLocalFile() ) { m_image = QImage( imageUrl.path() ); return; } debug() << "Image is remote, handled by podcastImageFetcher."; } Podcasts::PodcastEpisodePtr SqlPodcastChannel::addEpisode( PodcastEpisodePtr episode ) { if( !m_provider ) return PodcastEpisodePtr(); - KUrl checkUrl; + QUrl checkUrl; //searched in the database for guid or enclosure url if( !episode->guid().isEmpty() ) checkUrl = episode->guid(); else if( !episode->uidUrl().isEmpty() ) checkUrl = episode->uidUrl(); else return PodcastEpisodePtr(); //noting to check for if( m_provider->possiblyContainsTrack( checkUrl ) ) return PodcastEpisodePtr::dynamicCast( m_provider->trackForUrl( episode->guid() ) ); //force episodes load. if( !m_episodesLoaded ) loadEpisodes(); SqlPodcastEpisodePtr sqlEpisode; if (SqlPodcastEpisodePtr::dynamicCast( episode )) sqlEpisode = SqlPodcastEpisodePtr( new SqlPodcastEpisode( episode ) ); else sqlEpisode = SqlPodcastEpisodePtr( new SqlPodcastEpisode( PodcastChannelPtr(this) , episode ) ); //episodes are sorted on pubDate high to low int i; for( i = 0; i < m_episodes.count() ; i++ ) { if( sqlEpisode->pubDate() > m_episodes[i]->pubDate() ) { m_episodes.insert( i, sqlEpisode ); break; } } //insert in case the list is empty or at the end of the list if( i == m_episodes.count() ) m_episodes << sqlEpisode; notifyObserversTrackAdded( Meta::TrackPtr::dynamicCast( sqlEpisode ), i ); applyPurge(); m_trackCacheIsValid = false; return PodcastEpisodePtr::dynamicCast( sqlEpisode ); } void SqlPodcastChannel::applyPurge() { DEBUG_BLOCK if( !hasPurge() ) return; if( m_episodes.count() > purgeCount() ) { int purgeIndex = 0; foreach( SqlPodcastEpisodePtr episode, m_episodes ) { if ( !episode->isKeep() ) { if( purgeIndex >= purgeCount() ) { m_provider->deleteDownloadedEpisode( episode ); m_episodes.removeOne( episode ); } else purgeIndex++; } } m_trackCacheIsValid = false; } } void SqlPodcastChannel::updateInDb() { SqlStorage *sqlStorage = StorageManager::instance()->sqlStorage(); QString boolTrue = sqlStorage->boolTrue(); QString boolFalse = sqlStorage->boolFalse(); #define escape(x) sqlStorage->escape(x) QString command; QTextStream stream( &command ); if( m_dbId ) { stream << "UPDATE podcastchannels "; stream << "SET url='"; stream << escape(m_url.url()); stream << "', title='"; stream << escape(m_title); stream << "', weblink='"; stream << escape(m_webLink.url()); stream << "', image='"; stream << escape(m_imageUrl.url()); stream << "', description='"; stream << escape(m_description); stream << "', copyright='"; stream << escape(m_copyright); stream << "', directory='"; stream << escape(m_directory.url()); stream << "', labels='"; stream << escape(m_labels.join( "," )); stream << "', subscribedate='"; stream << escape(m_subscribeDate.toString()); stream << "', autoscan="; stream << (m_autoScan ? boolTrue : boolFalse); stream << ", fetchtype="; stream << QString::number(m_fetchType); stream << ", haspurge="; stream << (m_purge ? boolTrue : boolFalse); stream << ", purgecount="; stream << QString::number(m_purgeCount); stream << ", writetags="; stream << (m_writeTags ? boolTrue : boolFalse); stream << ", filenamelayout='"; stream << escape(m_filenameLayout); stream << "' WHERE id="; stream << m_dbId; stream << ";"; kDebug() << command; sqlStorage->query( command ); } else { stream << "INSERT INTO podcastchannels("; stream << "url,title,weblink,image,description,copyright,directory,labels,"; stream << "subscribedate,autoscan,fetchtype,haspurge,purgecount,writetags,filenamelayout) "; stream << "VALUES ( '"; stream << escape(m_url.url()) << "', '"; stream << escape(m_title) << "', '"; stream << escape(m_webLink.url()) << "', '"; stream << escape(m_imageUrl.url()) << "', '"; stream << escape(m_description) << "', '"; stream << escape(m_copyright) << "', '"; stream << escape(m_directory.url()) << "', '"; stream << escape(m_labels.join( "," )) << "', '"; stream << escape(m_subscribeDate.toString()) << "', "; stream << (m_autoScan ? boolTrue : boolFalse) << ", "; stream << QString::number(m_fetchType) << ", "; stream << (m_purge ? boolTrue : boolFalse) << ", "; stream << QString::number(m_purgeCount) << ", "; stream << (m_writeTags ? boolTrue : boolFalse) << ", '"; stream << escape(m_filenameLayout); stream << "');"; kDebug() << command; m_dbId = sqlStorage->insert( command, "podcastchannels" ); } } void SqlPodcastChannel::deleteFromDb() { SqlStorage *sqlStorage = StorageManager::instance()->sqlStorage(); foreach( SqlPodcastEpisodePtr sqlEpisode, m_episodes ) { sqlEpisode->deleteFromDb(); m_episodes.removeOne( sqlEpisode ); } m_trackCacheIsValid = false; sqlStorage->query( QString( "DELETE FROM podcastchannels WHERE id = %1;" ).arg( dbId() ) ); } void SqlPodcastChannel::loadEpisodes() { m_episodes.clear(); SqlStorage *sqlStorage = StorageManager::instance()->sqlStorage(); //If purge is enabled we must limit the number of results QString command; int rowLength = 15; //If purge is enabled we must limit the number of results, though there are some files //the user want to be shown even if there is no more slot if( hasPurge() ) { command = QString( "(SELECT id, url, channel, localurl, guid, " "title, subtitle, sequencenumber, description, mimetype, pubdate, " "duration, filesize, isnew, iskeep FROM podcastepisodes WHERE channel = %1 " "AND iskeep IS FALSE ORDER BY pubdate DESC LIMIT " + QString::number( purgeCount() ) + ") " "UNION " "(SELECT id, url, channel, localurl, guid, " "title, subtitle, sequencenumber, description, mimetype, pubdate, " "duration, filesize, isnew, iskeep FROM podcastepisodes WHERE channel = %1 " "AND iskeep IS TRUE) " "ORDER BY pubdate DESC;" ); } else { command = QString( "SELECT id, url, channel, localurl, guid, " "title, subtitle, sequencenumber, description, mimetype, pubdate, " "duration, filesize, isnew, iskeep FROM podcastepisodes WHERE channel = %1 " "ORDER BY pubdate DESC;" ); } QStringList results = sqlStorage->query( command.arg( m_dbId ) ); for( int i = 0; i < results.size(); i += rowLength ) { QStringList episodesResult = results.mid( i, rowLength ); SqlPodcastEpisodePtr sqlEpisode = SqlPodcastEpisodePtr( new SqlPodcastEpisode( episodesResult, SqlPodcastChannelPtr( this ) ) ); m_episodes << sqlEpisode; } m_episodesLoaded = true; m_trackCacheIsValid = false; } Meta::TrackList Podcasts::SqlPodcastChannel::tracks() { if ( !m_trackCacheIsValid ) { m_episodesAsTracksCache = Podcasts::SqlPodcastEpisode::toTrackList( m_episodes ); m_trackCacheIsValid = true; } return m_episodesAsTracksCache; } void Podcasts::SqlPodcastChannel::syncTrackStatus( int position, Meta::TrackPtr otherTrack ) { Q_UNUSED( position ); Podcasts::PodcastEpisodePtr master = Podcasts::PodcastEpisodePtr::dynamicCast( otherTrack ); if ( master ) { this->setName( master->channel()->name() ); this->setTitle( master->channel()->title() ); this->setUrl( master->channel()->url() ); } } void Podcasts::SqlPodcastChannel::addTrack( Meta::TrackPtr track, int position ) { Q_UNUSED( position ); addEpisode( Podcasts::PodcastEpisodePtr::dynamicCast( track ) ); notifyObserversTrackAdded( track, position ); } diff --git a/src/core-impl/podcasts/sql/SqlPodcastMeta.h b/src/core-impl/podcasts/sql/SqlPodcastMeta.h index ed346bacd0..b488798b78 100644 --- a/src/core-impl/podcasts/sql/SqlPodcastMeta.h +++ b/src/core-impl/podcasts/sql/SqlPodcastMeta.h @@ -1,171 +1,171 @@ /**************************************************************************************** * Copyright (c) 2008 Bart Cerneels * * * * 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, see . * ****************************************************************************************/ #ifndef SQLPODCASTMETA_H #define SQLPODCASTMETA_H #include "core/podcasts/PodcastMeta.h" #include "core-impl/meta/file/File.h" #include "core/playlists/PlaylistProvider.h" namespace Podcasts { class SqlPodcastEpisode; class SqlPodcastChannel; class SqlPodcastProvider; typedef KSharedPtr SqlPodcastEpisodePtr; typedef KSharedPtr SqlPodcastChannelPtr; typedef QList SqlPodcastEpisodeList; typedef QList SqlPodcastChannelList; class SqlPodcastEpisode : public Podcasts::PodcastEpisode { public: static Meta::TrackList toTrackList( SqlPodcastEpisodeList episodes ); static PodcastEpisodeList toPodcastEpisodeList( SqlPodcastEpisodeList episodes ); SqlPodcastEpisode( const QStringList &queryResult, SqlPodcastChannelPtr sqlChannel ); /** Copy from another PodcastEpisode */ SqlPodcastEpisode( PodcastEpisodePtr episode ); SqlPodcastEpisode( PodcastChannelPtr channel, PodcastEpisodePtr episode ); ~SqlPodcastEpisode(); //PodcastEpisode methods PodcastChannelPtr channel() const { return PodcastChannelPtr::dynamicCast( m_channel ); } virtual bool isKeep() const { return m_isKeep; } virtual void setNew( bool isNew ); virtual void setKeep( bool isKeep ); - virtual void setLocalUrl( const KUrl &url ); + virtual void setLocalUrl( const QUrl &url ); //Track Methods virtual QString name() const; virtual QString prettyName() const; virtual void setTitle( const QString &title ); virtual qint64 length() const; virtual bool hasCapabilityInterface( Capabilities::Capability::Type type ) const; virtual Capabilities::Capability* createCapabilityInterface( Capabilities::Capability::Type type ); virtual void finishedPlaying( double playedFraction ); virtual Meta::ArtistPtr artist() const; virtual Meta::ComposerPtr composer() const; virtual Meta::GenrePtr genre() const; virtual Meta::YearPtr year() const; virtual Meta::TrackEditorPtr editor(); //SqlPodcastEpisode specific methods bool writeTagsToFile(); int dbId() const { return m_dbId; } void updateInDb(); void deleteFromDb(); private: /** * Establishes m_localFile using MetaProxy::Track if m_localUrl is valid. */ void setupLocalFile(); int m_dbId; //database ID bool m_isKeep; //Keep the download after purge or not? SqlPodcastChannelPtr m_channel; //the parent of this episode Meta::TrackPtr m_localFile; }; class SqlPodcastChannel : public Podcasts::PodcastChannel { public: static Playlists::PlaylistPtr toPlaylistPtr( SqlPodcastChannelPtr sqlChannel ); static SqlPodcastChannelPtr fromPlaylistPtr( Playlists::PlaylistPtr playlist ); SqlPodcastChannel( SqlPodcastProvider *provider, const QStringList &queryResult ); /** Copy a PodcastChannel */ SqlPodcastChannel( SqlPodcastProvider *provider, PodcastChannelPtr channel ); ~SqlPodcastChannel(); // Playlists::Playlist methods virtual void syncTrackStatus( int position, Meta::TrackPtr otherTrack ); virtual int trackCount() const; virtual QString filenameLayout() const { return m_filenameLayout; } virtual Meta::TrackList tracks(); virtual void addTrack( Meta::TrackPtr track, int position = -1 ); virtual void triggerTrackLoad(); virtual Playlists::PlaylistProvider *provider() const; virtual QStringList groups(); virtual void setGroups( const QStringList &groups ); //Podcasts::PodcastChannel methods - virtual KUrl uidUrl() const; + virtual QUrl uidUrl() const; virtual void setTitle( const QString &title ); virtual Podcasts::PodcastEpisodeList episodes() const; virtual bool hasImage() const { return !m_image.isNull(); } virtual void setImage( const QImage &image ); virtual QImage image() const { return m_image; } - virtual KUrl imageUrl() const { return m_imageUrl; } - virtual void setImageUrl( const KUrl &imageUrl ); + virtual QUrl imageUrl() const { return m_imageUrl; } + virtual void setImageUrl( const QUrl &imageUrl ); virtual void setFilenameLayout( const QString &filenameLayout ) { m_filenameLayout = filenameLayout; } PodcastEpisodePtr addEpisode( PodcastEpisodePtr episode ); //SqlPodcastChannel specific methods int dbId() const { return m_dbId; } //void addEpisode( SqlPodcastEpisodePtr episode ) { m_episodes << episode; } bool writeTags() const { return m_writeTags; } void setWriteTags( bool writeTags ) { m_writeTags = writeTags; } void updateInDb(); void deleteFromDb(); const SqlPodcastEpisodeList sqlEpisodes() { return m_episodes; } void loadEpisodes(); void applyPurge(); private: bool m_writeTags; int m_dbId; //database ID bool m_episodesLoaded; SqlPodcastEpisodeList m_episodes; bool m_trackCacheIsValid; Meta::TrackList m_episodesAsTracksCache; SqlPodcastProvider *m_provider; QString m_filenameLayout; //specifies filename layout for episodes }; } //namespace Podcasts Q_DECLARE_METATYPE( Podcasts::SqlPodcastEpisodePtr ) Q_DECLARE_METATYPE( Podcasts::SqlPodcastEpisodeList ) Q_DECLARE_METATYPE( Podcasts::SqlPodcastChannelPtr ) Q_DECLARE_METATYPE( Podcasts::SqlPodcastChannelList ) #endif diff --git a/src/core-impl/podcasts/sql/SqlPodcastProvider.cpp b/src/core-impl/podcasts/sql/SqlPodcastProvider.cpp index 99ef26cd59..0a5b076e78 100644 --- a/src/core-impl/podcasts/sql/SqlPodcastProvider.cpp +++ b/src/core-impl/podcasts/sql/SqlPodcastProvider.cpp @@ -1,1629 +1,1632 @@ /**************************************************************************************** * Copyright (c) 2007-2009 Bart Cerneels * * Copyright (c) 2009 Frank Meerkoetter * * * * 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, see . * ****************************************************************************************/ #include "SqlPodcastProvider.h" #include "MainWindow.h" #include "OpmlWriter.h" #include "SvgHandler.h" #include "QStringx.h" #include "browsers/playlistbrowser/PodcastModel.h" #include "context/popupdropper/libpud/PopupDropper.h" #include "context/popupdropper/libpud/PopupDropperItem.h" #include #include "core/interfaces/Logger.h" #include "core/podcasts/PodcastImageFetcher.h" #include "core/podcasts/PodcastReader.h" #include "core/support/Amarok.h" #include "core/support/Components.h" #include "core/support/Debug.h" #include "core-impl/storage/StorageManager.h" #include "core-impl/podcasts/sql/PodcastSettingsDialog.h" #include "playlistmanager/sql/SqlPlaylistGroup.h" #include "ui_SqlPodcastProviderSettingsWidget.h" #include #include #include #include #include #include #include #include #include -#include +#include #include #include #include #include #include #include #include using namespace Podcasts; static const int PODCAST_DB_VERSION = 6; static const QString key( "AMAROK_PODCAST" ); static const QString PODCAST_TMP_POSTFIX( ".tmp" ); SqlPodcastProvider::SqlPodcastProvider() : m_updateTimer( new QTimer( this ) ) , m_updatingChannels( 0 ) , m_completedDownloads( 0 ) , m_providerSettingsDialog( 0 ) , m_providerSettingsWidget( 0 ) , m_configureChannelAction( 0 ) , m_deleteAction( 0 ) , m_downloadAction( 0 ) , m_keepAction( 0 ) , m_removeAction( 0 ) , m_updateAction( 0 ) , m_writeTagsAction( 0 ) , m_podcastImageFetcher( 0 ) { connect( m_updateTimer, SIGNAL(timeout()), SLOT(autoUpdate()) ); SqlStorage *sqlStorage = StorageManager::instance()->sqlStorage(); if( !sqlStorage ) { error() << "Could not get a SqlStorage instance"; return; } m_autoUpdateInterval = Amarok::config( "Podcasts" ) .readEntry( "AutoUpdate Interval", 30 ); m_maxConcurrentDownloads = Amarok::config( "Podcasts" ) .readEntry( "Maximum Simultaneous Downloads", 4 ); m_maxConcurrentUpdates = Amarok::config( "Podcasts" ) .readEntry( "Maximum Simultaneous Updates", 4 ); m_baseDownloadDir = Amarok::config( "Podcasts" ).readEntry( "Base Download Directory", Amarok::saveLocation( "podcasts" ) ); QStringList values; values = sqlStorage->query( QString( "SELECT version FROM admin WHERE component = '%1';" ) .arg( sqlStorage->escape( key ) ) ); if( values.isEmpty() ) { debug() << "creating Podcast Tables"; createTables(); sqlStorage->query( "INSERT INTO admin(component,version) " "VALUES('" + key + "'," + QString::number( PODCAST_DB_VERSION ) + ");" ); } else { int version = values.first().toInt(); if( version == PODCAST_DB_VERSION ) loadPodcasts(); else updateDatabase( version /*from*/, PODCAST_DB_VERSION /*to*/ ); startTimer(); } } void SqlPodcastProvider::startTimer() { if( !m_autoUpdateInterval ) return; //timer is disabled if( m_updateTimer->isActive() && m_updateTimer->interval() == ( m_autoUpdateInterval * 1000 * 60 ) ) return; //already started with correct interval //and only start if at least one channel has autoscan enabled foreach( Podcasts::SqlPodcastChannelPtr channel, m_channels ) { if( channel->autoScan() ) { m_updateTimer->start( 1000 * 60 * m_autoUpdateInterval ); return; } } } SqlPodcastProvider::~SqlPodcastProvider() { foreach( Podcasts::SqlPodcastChannelPtr channel, m_channels ) { channel->updateInDb(); foreach( Podcasts::SqlPodcastEpisodePtr episode, channel->sqlEpisodes() ) episode->updateInDb(); } m_channels.clear(); Amarok::config( "Podcasts" ) .writeEntry( "AutoUpdate Interval", m_autoUpdateInterval ); Amarok::config( "Podcasts" ) .writeEntry( "Maximum Simultaneous Downloads", m_maxConcurrentDownloads ); Amarok::config( "Podcasts" ) .writeEntry( "Maximum Simultaneous Updates", m_maxConcurrentUpdates ); } void SqlPodcastProvider::loadPodcasts() { m_channels.clear(); SqlStorage *sqlStorage = StorageManager::instance()->sqlStorage(); if( !sqlStorage ) return; QStringList results = sqlStorage->query( "SELECT id, url, title, weblink, image" ", description, copyright, directory, labels, subscribedate, autoscan, fetchtype" ", haspurge, purgecount, writetags, filenamelayout FROM podcastchannels;" ); int rowLength = 16; for( int i = 0; i < results.size(); i += rowLength ) { QStringList channelResult = results.mid( i, rowLength ); SqlPodcastChannelPtr channel = SqlPodcastChannelPtr( new SqlPodcastChannel( this, channelResult ) ); if( channel->image().isNull() ) fetchImage( channel ); m_channels << channel; } if( m_podcastImageFetcher ) m_podcastImageFetcher->run(); emit updated(); } SqlPodcastEpisodePtr SqlPodcastProvider::sqlEpisodeForString( const QString &string ) { if( string.isEmpty() ) return SqlPodcastEpisodePtr(); SqlStorage *sqlStorage = StorageManager::instance()->sqlStorage(); if( !sqlStorage ) return SqlPodcastEpisodePtr(); QString command = "SELECT id, url, channel, localurl, guid, " "title, subtitle, sequencenumber, description, mimetype, pubdate, " "duration, filesize, isnew, iskeep FROM podcastepisodes " "WHERE guid='%1' OR url='%1' OR localurl='%1' ORDER BY id DESC;"; command = command.arg( sqlStorage->escape( string ) ); QStringList dbResult = sqlStorage->query( command ); if( dbResult.isEmpty() ) return SqlPodcastEpisodePtr(); int episodeId = dbResult[0].toInt(); int channelId = dbResult[2].toInt(); bool found = false; Podcasts::SqlPodcastChannelPtr channel; foreach( channel, m_channels ) { if( channel->dbId() == channelId ) { found = true; break; } } if( !found ) { error() << QString( "There is a track in the database with url/guid=%1 (%2) " "but there is no channel with dbId=%3 in our list!" ) .arg( string ).arg( episodeId ).arg( channelId ); return SqlPodcastEpisodePtr(); } Podcasts::SqlPodcastEpisodePtr episode; foreach( episode, channel->sqlEpisodes() ) if( episode->dbId() == episodeId ) return episode; //The episode was found in the database but it's channel didn't have it in it's list. //That probably is because it's beyond the purgecount limit or the tracks were not loaded yet. return SqlPodcastEpisodePtr( new SqlPodcastEpisode( dbResult.mid( 0, 15 ), channel ) ); } bool -SqlPodcastProvider::possiblyContainsTrack( const KUrl &url ) const +SqlPodcastProvider::possiblyContainsTrack( const QUrl &url ) const { SqlStorage *sqlStorage = StorageManager::instance()->sqlStorage(); if( !sqlStorage ) return false; QString command = "SELECT id FROM podcastepisodes WHERE guid='%1' OR url='%1' " "OR localurl='%1';"; command = command.arg( sqlStorage->escape( url.url() ) ); QStringList dbResult = sqlStorage->query( command ); return !dbResult.isEmpty(); } Meta::TrackPtr -SqlPodcastProvider::trackForUrl( const KUrl &url ) +SqlPodcastProvider::trackForUrl( const QUrl &url ) { if( url.isEmpty() ) return Meta::TrackPtr(); SqlPodcastEpisodePtr episode = sqlEpisodeForString( url.url() ); return Meta::TrackPtr::dynamicCast( episode ); } Playlists::PlaylistList SqlPodcastProvider::playlists() { Playlists::PlaylistList playlistList; QListIterator i( m_channels ); while( i.hasNext() ) { playlistList << Playlists::PlaylistPtr::staticCast( i.next() ); } return playlistList; } QActionList SqlPodcastProvider::providerActions() { if( m_providerActions.isEmpty() ) { QAction *updateAllAction = new QAction( KIcon( "view-refresh-amarok" ), i18n( "&Update All Channels" ), this ); updateAllAction->setProperty( "popupdropper_svg_id", "update" ); connect( updateAllAction, SIGNAL(triggered()), this, SLOT(updateAll()) ); m_providerActions << updateAllAction; QAction *configureAction = new QAction( KIcon( "configure" ), i18n( "&Configure General Settings" ), this ); configureAction->setProperty( "popupdropper_svg_id", "configure" ); connect( configureAction, SIGNAL(triggered()), this, SLOT(slotConfigureProvider()) ); m_providerActions << configureAction; QAction *exportOpmlAction = new QAction( KIcon( "document-export" ), i18n( "&Export subscriptions to OPML file" ), this ); connect( exportOpmlAction, SIGNAL(triggered()), SLOT(slotExportOpml()) ); m_providerActions << exportOpmlAction; } return m_providerActions; } QActionList SqlPodcastProvider::playlistActions( const Playlists::PlaylistList &playlists ) { QActionList actions; SqlPodcastChannelList sqlChannels; foreach( const Playlists::PlaylistPtr &playlist, playlists ) { SqlPodcastChannelPtr sqlChannel = SqlPodcastChannel::fromPlaylistPtr( playlist ); if( sqlChannel ) sqlChannels << sqlChannel; } if( sqlChannels.isEmpty() ) return actions; //TODO: add export OPML action for selected playlists only. Use the QAction::data() trick. if( m_configureChannelAction == 0 ) { m_configureChannelAction = new QAction( KIcon( "configure" ), i18n( "&Configure" ), this ); m_configureChannelAction->setProperty( "popupdropper_svg_id", "configure" ); connect( m_configureChannelAction, SIGNAL(triggered()), SLOT(slotConfigureChannel()) ); } //only one channel can be configured at a time. if( sqlChannels.count() == 1 ) { m_configureChannelAction->setData( QVariant::fromValue( sqlChannels.first() ) ); actions << m_configureChannelAction; } if( m_removeAction == 0 ) { m_removeAction = new QAction( KIcon( "news-unsubscribe" ), i18n( "&Remove Subscription" ), this ); m_removeAction->setProperty( "popupdropper_svg_id", "remove" ); connect( m_removeAction, SIGNAL(triggered()), SLOT(slotRemoveChannels()) ); } m_removeAction->setData( QVariant::fromValue( sqlChannels ) ); actions << m_removeAction; if( m_updateAction == 0 ) { m_updateAction = new QAction( KIcon( "view-refresh-amarok" ), i18n( "&Update Channel" ), this ); m_updateAction->setProperty( "popupdropper_svg_id", "update" ); connect( m_updateAction, SIGNAL(triggered()), SLOT(slotUpdateChannels()) ); } m_updateAction->setData( QVariant::fromValue( sqlChannels ) ); actions << m_updateAction; return actions; } QActionList SqlPodcastProvider::trackActions( const QMultiHash &playlistTracks ) { SqlPodcastEpisodeList episodes; foreach( const Playlists::PlaylistPtr &playlist, playlistTracks.uniqueKeys() ) { SqlPodcastChannelPtr sqlChannel = SqlPodcastChannel::fromPlaylistPtr( playlist ); if( !sqlChannel ) continue; SqlPodcastEpisodeList channelEpisodes = sqlChannel->sqlEpisodes(); QList trackPositions = playlistTracks.values( playlist ); qSort( trackPositions ); foreach( int trackPosition, trackPositions ) { if( trackPosition >= 0 && trackPosition < channelEpisodes.count() ) episodes << channelEpisodes.at( trackPosition ); } } QActionList actions; if( episodes.isEmpty() ) return actions; if( m_downloadAction == 0 ) { m_downloadAction = new QAction( KIcon( "go-down" ), i18n( "&Download Episode" ), this ); m_downloadAction->setProperty( "popupdropper_svg_id", "download" ); connect( m_downloadAction, SIGNAL(triggered()), SLOT(slotDownloadEpisodes()) ); } if( m_deleteAction == 0 ) { m_deleteAction = new QAction( KIcon( "edit-delete" ), i18n( "&Delete Downloaded Episode" ), this ); m_deleteAction->setProperty( "popupdropper_svg_id", "delete" ); m_deleteAction->setObjectName( "deleteAction" ); connect( m_deleteAction, SIGNAL(triggered()), SLOT(slotDeleteDownloadedEpisodes()) ); } if( m_writeTagsAction == 0 ) { m_writeTagsAction = new QAction( KIcon( "media-track-edit-amarok" ), i18n( "&Write Feed Information to File" ), this ); m_writeTagsAction->setProperty( "popupdropper_svg_id", "edit" ); connect( m_writeTagsAction, SIGNAL(triggered()), SLOT(slotWriteTagsToFiles()) ); } if( m_keepAction == 0 ) { m_keepAction = new QAction( KIcon( "podcast-amarok" ), i18n( "&Keep downloaded file" ), this ); m_keepAction->setToolTip( i18n( "Toggle the \"keep\" downloaded file status of " "this podcast episode. Downloaded files with this status wouldn't be " "deleted even if we apply a purge." ) ); m_keepAction->setProperty( "popupdropper_svg_id", "keep" ); m_keepAction->setCheckable( true ); connect( m_keepAction, SIGNAL(triggered(bool)), SLOT(slotSetKeep()) ); } SqlPodcastEpisodeList remoteEpisodes; SqlPodcastEpisodeList keptDownloadedEpisodes, unkeptDownloadedEpisodes; foreach( const SqlPodcastEpisodePtr &episode, episodes ) { if( episode->localUrl().isEmpty() ) remoteEpisodes << episode; else { if( episode->isKeep() ) keptDownloadedEpisodes << episode; else unkeptDownloadedEpisodes << episode; } } if( !remoteEpisodes.isEmpty() ) { m_downloadAction->setData( QVariant::fromValue( remoteEpisodes ) ); actions << m_downloadAction; } if( !( keptDownloadedEpisodes + unkeptDownloadedEpisodes ).isEmpty() ) { m_deleteAction->setData( QVariant::fromValue( keptDownloadedEpisodes + unkeptDownloadedEpisodes ) ); actions << m_deleteAction; m_keepAction->setChecked( unkeptDownloadedEpisodes.isEmpty() ); m_keepAction->setData( QVariant::fromValue( keptDownloadedEpisodes + unkeptDownloadedEpisodes ) ); actions << m_keepAction; } return actions; } Podcasts::PodcastEpisodePtr SqlPodcastProvider::episodeForGuid( const QString &guid ) { return PodcastEpisodePtr::dynamicCast( sqlEpisodeForString( guid ) ); } void -SqlPodcastProvider::addPodcast( const KUrl &url ) +SqlPodcastProvider::addPodcast( const QUrl &url ) { - KUrl kurl = KUrl( url ); + QUrl kurl = QUrl( url ); debug() << "importing " << kurl.url(); SqlStorage *sqlStorage = StorageManager::instance()->sqlStorage(); if( !sqlStorage ) return; QString command = "SELECT title FROM podcastchannels WHERE url='%1';"; command = command.arg( sqlStorage->escape( kurl.url() ) ); QStringList dbResult = sqlStorage->query( command ); if( !dbResult.isEmpty() ) { //Already subscribed to this Channel //notify the user. Amarok::Components::logger()->longMessage( i18n( "Already subscribed to %1.", dbResult.first() ), Amarok::Logger::Error ); } else { subscribe( kurl ); } } void SqlPodcastProvider::updateAll() { foreach( Podcasts::SqlPodcastChannelPtr channel, m_channels ) updateSqlChannel( channel ); } void -SqlPodcastProvider::subscribe( const KUrl &url ) +SqlPodcastProvider::subscribe( const QUrl &url ) { if( !url.isValid() ) return; if( m_updatingChannels >= m_maxConcurrentUpdates ) { debug() << QString( "Maximum concurrent updates (%1) reached. " "Queueing \"%2\" for subscribing." ) .arg( m_maxConcurrentUpdates ) .arg( url.url() ); m_subscribeQueue << url; return; } PodcastReader *podcastReader = new PodcastReader( this ); connect( podcastReader, SIGNAL(finished(PodcastReader*)), SLOT(slotReadResult(PodcastReader*)) ); connect( podcastReader, SIGNAL(statusBarSorryMessage(QString)), this, SLOT(slotStatusBarSorryMessage(QString)) ); connect( podcastReader, SIGNAL(statusBarNewProgressOperation( KIO::TransferJob *, const QString &, Podcasts::PodcastReader* )), SLOT(slotStatusBarNewProgressOperation( KIO::TransferJob *, const QString &, Podcasts::PodcastReader* )) ); m_updatingChannels++; podcastReader->read( url ); } Podcasts::PodcastChannelPtr SqlPodcastProvider::addChannel( Podcasts::PodcastChannelPtr channel ) { Podcasts::SqlPodcastChannelPtr sqlChannel = SqlPodcastChannelPtr( new Podcasts::SqlPodcastChannel( this, channel ) ); m_channels << sqlChannel; if( sqlChannel->episodes().count() == 0 ) updateSqlChannel( sqlChannel ); emit playlistAdded( Playlists::PlaylistPtr( sqlChannel.data() ) ); return PodcastChannelPtr( sqlChannel.data() ); } Podcasts::PodcastEpisodePtr SqlPodcastProvider::addEpisode( Podcasts::PodcastEpisodePtr episode ) { Podcasts::SqlPodcastEpisodePtr sqlEpisode = Podcasts::SqlPodcastEpisodePtr::dynamicCast( episode ); if( sqlEpisode.isNull() ) return Podcasts::PodcastEpisodePtr(); if( sqlEpisode->channel().isNull() ) { debug() << "channel is null"; return Podcasts::PodcastEpisodePtr(); } if( sqlEpisode->channel()->fetchType() == Podcasts::PodcastChannel::DownloadWhenAvailable ) downloadEpisode( sqlEpisode ); return Podcasts::PodcastEpisodePtr::dynamicCast( sqlEpisode ); } Podcasts::PodcastChannelList SqlPodcastProvider::channels() { PodcastChannelList list; QListIterator i( m_channels ); while( i.hasNext() ) { list << PodcastChannelPtr::dynamicCast( i.next() ); } return list; } void SqlPodcastProvider::removeSubscription( Podcasts::SqlPodcastChannelPtr sqlChannel ) { debug() << "Deleting channel " << sqlChannel->title(); sqlChannel->deleteFromDb(); m_channels.removeOne( sqlChannel ); //HACK: because of a database "leak" in the past we have orphan data in the tables. //Remove it when we know it's supposed to be empty. if( m_channels.isEmpty() ) { SqlStorage *sqlStorage = StorageManager::instance()->sqlStorage(); if( !sqlStorage ) return; debug() << "Unsubscribed from last channel, cleaning out the podcastepisodes table."; sqlStorage->query( "DELETE FROM podcastepisodes WHERE 1;" ); } emit playlistRemoved( Playlists::PlaylistPtr::dynamicCast( sqlChannel ) ); } void SqlPodcastProvider::configureProvider() { m_providerSettingsDialog = new KDialog( The::mainWindow() ); QWidget *settingsWidget = new QWidget( m_providerSettingsDialog ); m_providerSettingsDialog->setObjectName( "SqlPodcastProviderSettings" ); Ui::SqlPodcastProviderSettingsWidget settings; m_providerSettingsWidget = &settings; settings.setupUi( settingsWidget ); settings.m_baseDirUrl->setMode( KFile::Directory ); settings.m_baseDirUrl->setUrl( m_baseDownloadDir ); settings.m_autoUpdateInterval->setValue( m_autoUpdateInterval ); settings.m_autoUpdateInterval->setPrefix( i18nc( "prefix to 'x minutes'", "every " ) ); settings.m_autoUpdateInterval->setSuffix( ki18np( " minute", " minutes" ) ); m_providerSettingsDialog->setButtons( KDialog::Ok | KDialog::Cancel | KDialog::Apply ); m_providerSettingsDialog->setMainWidget( settingsWidget ); connect( settings.m_baseDirUrl, SIGNAL(textChanged(QString)), SLOT(slotConfigChanged()) ); connect( settings.m_autoUpdateInterval, SIGNAL(valueChanged(int)), SLOT(slotConfigChanged()) ); m_providerSettingsDialog->setWindowTitle( i18n( "Configure Local Podcasts" ) ); m_providerSettingsDialog->enableButtonApply( false ); if( m_providerSettingsDialog->exec() == QDialog::Accepted ) { m_autoUpdateInterval = settings.m_autoUpdateInterval->value(); if( m_autoUpdateInterval ) startTimer(); else m_updateTimer->stop(); - KUrl adjustedNewPath = settings.m_baseDirUrl->url(); - adjustedNewPath.adjustPath( KUrl::RemoveTrailingSlash ); + QUrl adjustedNewPath = settings.m_baseDirUrl->url(); + adjustedNewPath.adjustedNewPath = adjustedNewPath.adjusted(QUrl::StripTrailingSlash)); if( adjustedNewPath != m_baseDownloadDir ) { m_baseDownloadDir = adjustedNewPath; Amarok::config( "Podcasts" ).writeEntry( "Base Download Directory", m_baseDownloadDir ); if( !m_channels.isEmpty() ) { //TODO: check if there actually are downloaded episodes KDialog moveAllDialog; moveAllDialog.setCaption( i18n( "Move Podcasts" ) ); KVBox *vbox = new KVBox( &moveAllDialog ); QString question( i18n( "Do you want to move all downloaded episodes to the " "new location?") ); QLabel *label = new QLabel( question, vbox ); label->setWordWrap( true ); label->setMaximumWidth( 400 ); moveAllDialog.setMainWidget( vbox ); moveAllDialog.setButtons( KDialog::Yes | KDialog::No ); if( moveAllDialog.exec() == KDialog::Yes ) { foreach( SqlPodcastChannelPtr sqlChannel, m_channels ) { - KUrl oldSaveLocation = sqlChannel->saveLocation(); - KUrl newSaveLocation = m_baseDownloadDir; - newSaveLocation.addPath( oldSaveLocation.fileName() ); + QUrl oldSaveLocation = sqlChannel->saveLocation(); + QUrl newSaveLocation = m_baseDownloadDir; + newSaveLocation = newSaveLocation.adjusted(QUrl::StripTrailingSlash); + newSaveLocation.setPath(newSaveLocation.path() + '/' + ( oldSaveLocation.fileName() )); sqlChannel->setSaveLocation( newSaveLocation ); debug() << newSaveLocation.path(); moveDownloadedEpisodes( sqlChannel ); if( !QDir().rmdir( oldSaveLocation.toLocalFile() ) ) debug() << "Could not remove old directory " << oldSaveLocation.toLocalFile(); } } } } } delete m_providerSettingsDialog; m_providerSettingsDialog = 0; m_providerSettingsWidget = 0; } void SqlPodcastProvider::slotConfigChanged() { if( !m_providerSettingsWidget ) return; if( m_providerSettingsWidget->m_autoUpdateInterval->value() != m_autoUpdateInterval || m_providerSettingsWidget->m_baseDirUrl->url() != m_baseDownloadDir ) { m_providerSettingsDialog->enableButtonApply( true ); } } void SqlPodcastProvider::slotExportOpml() { QList rootOutlines; QMap headerData; //TODO: set header data such as date //TODO: folder outline support foreach( SqlPodcastChannelPtr channel, m_channels ) { OpmlOutline *channelOutline = new OpmlOutline(); #define addAttr( k, v ) channelOutline->addAttribute( k, v ) addAttr( "text", channel->title() ); addAttr( "type", "rss" ); addAttr( "xmlUrl", channel->url().url() ); rootOutlines << channelOutline; } //TODO: add checkbox as widget to filedialog to include podcast settings. - KFileDialog fileDialog( KUrl( "kfiledialog:///podcast/amarok_podcasts.opml"), "*.opml", + KFileDialog fileDialog( QUrl("kfiledialog:///podcast/amarok_podcasts.opml"), "*.opml", The::mainWindow() ); fileDialog.setMode( KFile::File ); fileDialog.setCaption( i18n( "Select file for OPML export") ); if( fileDialog.exec() != KDialog::Accepted ) return; - KUrl filePath = fileDialog.selectedUrl(); + QUrl filePath = fileDialog.selectedUrl(); QFile *opmlFile = new QFile( filePath.toLocalFile(), this ); if( !opmlFile->open( QIODevice::WriteOnly | QIODevice::Truncate ) ) { error() << "could not open OPML file " << filePath.url(); return; } OpmlWriter *opmlWriter = new OpmlWriter( rootOutlines, headerData, opmlFile ); connect( opmlWriter, SIGNAL(result(int)), SLOT(slotOpmlWriterDone(int)) ); opmlWriter->run(); } void SqlPodcastProvider::slotOpmlWriterDone( int result ) { Q_UNUSED( result ) OpmlWriter *writer = qobject_cast( QObject::sender() ); Q_ASSERT( writer ); writer->device()->close(); delete writer; } void SqlPodcastProvider::configureChannel( Podcasts::SqlPodcastChannelPtr sqlChannel ) { if( !sqlChannel ) return; - KUrl oldUrl = sqlChannel->url(); - KUrl oldSaveLocation = sqlChannel->saveLocation(); + QUrl oldUrl = sqlChannel->url(); + QUrl oldSaveLocation = sqlChannel->saveLocation(); bool oldHasPurge = sqlChannel->hasPurge(); int oldPurgeCount = sqlChannel->purgeCount(); bool oldAutoScan = sqlChannel->autoScan(); PodcastSettingsDialog dialog( sqlChannel, The::mainWindow() ); dialog.configure(); sqlChannel->updateInDb(); if( ( oldHasPurge && !sqlChannel->hasPurge() ) || ( oldPurgeCount < sqlChannel->purgeCount() ) ) { /* changed from purge to no-purge or increase purge count: we need to reload all episodes from the database. */ sqlChannel->loadEpisodes(); } else sqlChannel->applyPurge(); emit updated(); if( oldSaveLocation != sqlChannel->saveLocation() ) { moveDownloadedEpisodes( sqlChannel ); if( !QDir().rmdir( oldSaveLocation.toLocalFile() ) ) debug() << "Could not remove old directory " << oldSaveLocation.toLocalFile(); } //if the url changed force an update. if( oldUrl != sqlChannel->url() ) updateSqlChannel( sqlChannel ); //start autoscan in case it wasn't already if( sqlChannel->autoScan() && !oldAutoScan ) startTimer(); } void SqlPodcastProvider::deleteDownloadedEpisodes( Podcasts::SqlPodcastEpisodeList &episodes ) { foreach( Podcasts::SqlPodcastEpisodePtr episode, episodes ) deleteDownloadedEpisode( episode ); } void SqlPodcastProvider::moveDownloadedEpisodes( Podcasts::SqlPodcastChannelPtr sqlChannel ) { debug() << QString( "We need to move downloaded episodes of \"%1\" to %2" ) .arg( sqlChannel->title() ) .arg( sqlChannel->saveLocation().prettyUrl() ); - KUrl::List filesToMove; + QList filesToMove; foreach( Podcasts::SqlPodcastEpisodePtr episode, sqlChannel->sqlEpisodes() ) { if( !episode->localUrl().isEmpty() ) { - KUrl newLocation = sqlChannel->saveLocation(); + QUrl newLocation = sqlChannel->saveLocation(); QDir dir( newLocation.toLocalFile() ); dir.mkpath( "." ); - newLocation.addPath( episode->localUrl().fileName() ); + newLocation = newLocation.adjusted(QUrl::StripTrailingSlash); + newLocation.setPath(newLocation.path() + '/' + ( episode->localUrl().fileName() )); debug() << "Moving from " << episode->localUrl() << " to " << newLocation; KIO::Job *moveJob = KIO::move( episode->localUrl(), newLocation, KIO::HideProgressInfo ); //wait until job is finished. if( KIO::NetAccess::synchronousRun( moveJob, The::mainWindow() ) ) episode->setLocalUrl( newLocation ); } } } void SqlPodcastProvider::slotDeleteDownloadedEpisodes() { QAction *action = qobject_cast( QObject::sender() ); if( action == 0 ) return; Podcasts::SqlPodcastEpisodeList episodes = action->data().value(); deleteDownloadedEpisodes( episodes ); } void SqlPodcastProvider::slotDownloadEpisodes() { QAction *action = qobject_cast( QObject::sender() ); if( action == 0 ) return; Podcasts::SqlPodcastEpisodeList episodes = action->data().value(); foreach( Podcasts::SqlPodcastEpisodePtr episode, episodes ) downloadEpisode( episode ); } void SqlPodcastProvider::slotSetKeep() { QAction *action = qobject_cast( QObject::sender() ); if( action == 0 ) return; Podcasts::SqlPodcastEpisodeList episodes = action->data().value(); foreach( Podcasts::SqlPodcastEpisodePtr episode, episodes ) episode->setKeep( action->isChecked() ); } QPair SqlPodcastProvider::confirmUnsubscribe( Podcasts::SqlPodcastChannelPtr channel ) { KDialog unsubscribeDialog; unsubscribeDialog.setCaption( i18n( "Unsubscribe" ) ); KVBox *vbox = new KVBox( &unsubscribeDialog ); QString question( i18n( "Do you really want to unsubscribe from \"%1\"?", channel->title() ) ); QLabel *label = new QLabel( question, vbox ); label->setWordWrap( true ); label->setMaximumWidth( 400 ); QCheckBox *deleteMediaCheckBox = new QCheckBox( i18n( "Delete downloaded episodes" ), vbox ); unsubscribeDialog.setMainWidget( vbox ); unsubscribeDialog.setButtons( KDialog::Ok | KDialog::Cancel ); QPair result; result.first = unsubscribeDialog.exec() == QDialog::Accepted; result.second = deleteMediaCheckBox->isChecked(); return result; } void SqlPodcastProvider::slotRemoveChannels() { QAction *action = qobject_cast( QObject::sender() ); if( action == 0 ) return; Podcasts::SqlPodcastChannelList channels = action->data().value(); foreach( Podcasts::SqlPodcastChannelPtr channel, channels ) { QPair result = confirmUnsubscribe( channel ); if( result.first ) { debug() << "unsubscribing " << channel->title(); if( result.second ) { debug() << "removing all episodes"; Podcasts::SqlPodcastEpisodeList sqlEpisodes = channel->sqlEpisodes(); deleteDownloadedEpisodes( sqlEpisodes ); } removeSubscription( channel ); } } } void SqlPodcastProvider::slotUpdateChannels() { QAction *action = qobject_cast( QObject::sender() ); if( action == 0 ) return; Podcasts::SqlPodcastChannelList channels = action->data().value(); foreach( Podcasts::SqlPodcastChannelPtr channel, channels ) updateSqlChannel( channel ); } void SqlPodcastProvider::slotDownloadProgress( KJob *job, unsigned long percent ) { Q_UNUSED( job ); Q_UNUSED( percent ); unsigned int totalDownloadPercentage = 0; foreach( const KJob *jobKey, m_downloadJobMap.keys() ) totalDownloadPercentage += jobKey->percent(); //keep the completed jobs in mind as well. totalDownloadPercentage += m_completedDownloads * 100; emit totalPodcastDownloadProgress( totalDownloadPercentage / ( m_downloadJobMap.count() + m_completedDownloads ) ); } void SqlPodcastProvider::slotWriteTagsToFiles() { QAction *action = qobject_cast( QObject::sender() ); if( action == 0 ) return; Podcasts::SqlPodcastEpisodeList episodes = action->data().value(); foreach( Podcasts::SqlPodcastEpisodePtr episode, episodes ) episode->writeTagsToFile(); } void SqlPodcastProvider::slotConfigureChannel() { QAction *action = qobject_cast( QObject::sender() ); if( action == 0 ) return; Podcasts::SqlPodcastChannelPtr podcastChannel = action->data().value(); if( !podcastChannel.isNull() ) configureChannel( podcastChannel ); } void SqlPodcastProvider::deleteDownloadedEpisode( Podcasts::SqlPodcastEpisodePtr episode ) { if( !episode || episode->localUrl().isEmpty() ) return; debug() << "deleting " << episode->title(); KIO::del( episode->localUrl(), KIO::HideProgressInfo ); - episode->setLocalUrl( KUrl() ); + episode->setLocalUrl( QUrl() ); emit episodeDeleted( Podcasts::PodcastEpisodePtr::dynamicCast( episode ) ); } Podcasts::SqlPodcastChannelPtr SqlPodcastProvider::podcastChannelForId( int podcastChannelId ) { QListIterator i( m_channels ); while( i.hasNext() ) { int id = i.next()->dbId(); if( id == podcastChannelId ) return i.previous(); } return Podcasts::SqlPodcastChannelPtr(); } void SqlPodcastProvider::completePodcastDownloads() { //check to see if there are still downloads in progress if( !m_downloadJobMap.isEmpty() ) { debug() << QString( "There are still %1 podcast download jobs running!" ) .arg( m_downloadJobMap.count() ); KProgressDialog progressDialog( The::mainWindow(), i18n( "Waiting for Podcast Downloads to Finish" ), i18np( "There is still a podcast download in progress", "There are still %1 podcast downloads in progress", m_downloadJobMap.count() ) ); progressDialog.setButtonText( i18n("Cancel Download and Quit.") ); m_completedDownloads = 0; foreach( KJob *job, m_downloadJobMap.keys() ) { connect( job, SIGNAL(percent(KJob*,ulong)), SLOT(slotDownloadProgress(KJob*,ulong)) ); } connect( this, SIGNAL(totalPodcastDownloadProgress(int)), progressDialog.progressBar(), SLOT(setValue(int)) ); int result = progressDialog.exec(); if( result == QDialog::Rejected ) { foreach( KJob *job, m_downloadJobMap.keys() ) { job->kill(); } } } } void SqlPodcastProvider::autoUpdate() { if( Solid::Networking::status() != Solid::Networking::Connected && Solid::Networking::status() != Solid::Networking::Unknown ) { debug() << "Solid reports we are not online, canceling podcast auto-update"; return; } foreach( Podcasts::SqlPodcastChannelPtr channel, m_channels ) { if( channel->autoScan() ) updateSqlChannel( channel ); } } void SqlPodcastProvider::updateSqlChannel( Podcasts::SqlPodcastChannelPtr channel ) { if( channel.isNull() ) return; if( m_updatingChannels >= m_maxConcurrentUpdates ) { debug() << QString( "Maximum concurrent updates (%1) reached. " "Queueing \"%2\" for download." ) .arg( m_maxConcurrentUpdates ) .arg( channel->title() ); m_updateQueue << channel; return; } PodcastReader *podcastReader = new PodcastReader( this ); connect( podcastReader, SIGNAL(finished(PodcastReader*)), SLOT(slotReadResult(PodcastReader*)) ); connect( podcastReader, SIGNAL(statusBarSorryMessage(QString)), this, SLOT(slotStatusBarSorryMessage(QString)) ); connect( podcastReader, SIGNAL(statusBarNewProgressOperation(KIO::TransferJob*,QString,Podcasts::PodcastReader*)), this, SLOT(slotStatusBarNewProgressOperation(KIO::TransferJob*,QString,Podcasts::PodcastReader*)) ); m_updatingChannels++; podcastReader->update( Podcasts::PodcastChannelPtr::dynamicCast( channel ) ); } void SqlPodcastProvider::slotReadResult( Podcasts::PodcastReader *podcastReader ) { if( podcastReader->error() != QXmlStreamReader::NoError ) { debug() << podcastReader->errorString(); Amarok::Components::logger()->longMessage( podcastReader->errorString(), Amarok::Logger::Error ); } debug() << "Finished updating: " << podcastReader->url(); --m_updatingChannels; debug() << "Updating counter reached " << m_updatingChannels; Podcasts::SqlPodcastChannelPtr channel = Podcasts::SqlPodcastChannelPtr::dynamicCast( podcastReader->channel() ); if( channel.isNull() ) { error() << "Could not cast to SqlPodcastChannel " << __FILE__ << ":" << __LINE__; return; } if( channel->image().isNull() ) { fetchImage( channel ); } channel->updateInDb(); podcastReader->deleteLater(); //first we work through the list of new subscriptions if( !m_subscribeQueue.isEmpty() ) { subscribe( m_subscribeQueue.takeFirst() ); } else if( !m_updateQueue.isEmpty() ) { updateSqlChannel( m_updateQueue.takeFirst() ); } else if( m_updatingChannels == 0 ) { //TODO: start downloading episodes here. if( m_podcastImageFetcher ) m_podcastImageFetcher->run(); } } void SqlPodcastProvider::slotStatusBarNewProgressOperation( KIO::TransferJob * job, const QString &description, Podcasts::PodcastReader* reader ) { Amarok::Components::logger()->newProgressOperation( job, description, reader, SLOT(slotAbort()) ); } void SqlPodcastProvider::downloadEpisode( Podcasts::SqlPodcastEpisodePtr sqlEpisode ) { if( sqlEpisode.isNull() ) { error() << "SqlPodcastProvider::downloadEpisode( Podcasts::SqlPodcastEpisodePtr sqlEpisode ) was called for a non-SqlPodcastEpisode"; return; } foreach( struct PodcastEpisodeDownload download, m_downloadJobMap ) { if( download.episode == sqlEpisode ) { debug() << "already downloading " << sqlEpisode->uidUrl(); return; } } if( m_downloadJobMap.size() >= m_maxConcurrentDownloads ) { debug() << QString( "Maximum concurrent downloads (%1) reached. " "Queueing \"%2\" for download." ) .arg( m_maxConcurrentDownloads ) .arg( sqlEpisode->title() ); //put into a FIFO which is used in downloadResult() to start a new download m_downloadQueue << sqlEpisode; return; } KIO::TransferJob *transferJob = KIO::get( sqlEpisode->uidUrl(), KIO::Reload, KIO::HideProgressInfo ); QFile *tmpFile = createTmpFile( sqlEpisode ); struct PodcastEpisodeDownload download = { sqlEpisode, tmpFile, /* Unless a redirect happens the filename from the enclosure is used. This is a potential source of filename conflicts in downloadResult() */ - KUrl( sqlEpisode->uidUrl() ).fileName(), + QUrl( sqlEpisode->uidUrl() ).fileName(), false }; m_downloadJobMap.insert( transferJob, download ); if( tmpFile->exists() ) { qint64 offset = tmpFile->size(); debug() << "temporary file exists, resume download from offset " << offset; QMap resumeData; resumeData.insert( "resume", QString::number( offset ) ); transferJob->addMetaData( resumeData ); } if( !tmpFile->open( QIODevice::WriteOnly | QIODevice::Append ) ) { Amarok::Components::logger()->longMessage( i18n( "Unable to save podcast episode file to %1", tmpFile->fileName() ) ); delete tmpFile; return; } debug() << "starting download for " << sqlEpisode->title() << " url: " << sqlEpisode->prettyUrl(); Amarok::Components::logger()->newProgressOperation( transferJob , sqlEpisode->title().isEmpty() ? i18n( "Downloading Podcast Media" ) : i18n( "Downloading Podcast \"%1\"" , sqlEpisode->title() ), transferJob, SLOT(kill()) ); connect( transferJob, SIGNAL(data(KIO::Job*,QByteArray)), SLOT(addData(KIO::Job*,QByteArray)) ); //need to connect to finished instead of result because it's always emitted. //We need to cleanup after a download is cancled regardless of the argument in //KJob::kill() connect( transferJob, SIGNAL(finished(KJob*)), SLOT(downloadResult(KJob*)) ); - connect( transferJob, SIGNAL(redirection(KIO::Job*,KUrl)), - SLOT(redirected(KIO::Job*,KUrl)) ); + connect( transferJob, SIGNAL(redirection(KIO::Job*,QUrl)), + SLOT(redirected(KIO::Job*,QUrl)) ); } void SqlPodcastProvider::downloadEpisode( Podcasts::PodcastEpisodePtr episode ) { downloadEpisode( SqlPodcastEpisodePtr::dynamicCast( episode ) ); } void SqlPodcastProvider::cleanupDownload( KJob *job, bool downloadFailed ) { struct PodcastEpisodeDownload download = m_downloadJobMap.value( job ); QFile *tmpFile = download.tmpFile; if( downloadFailed && tmpFile ) { debug() << "deleting temporary podcast file: " << tmpFile->fileName(); tmpFile->remove(); } m_downloadJobMap.remove( job ); delete tmpFile; } QFile * SqlPodcastProvider::createTmpFile( Podcasts::SqlPodcastEpisodePtr sqlEpisode ) { if( sqlEpisode.isNull() ) { error() << "sqlEpisodePtr is NULL after download"; return 0; } Podcasts::SqlPodcastChannelPtr sqlChannel = Podcasts::SqlPodcastChannelPtr::dynamicCast( sqlEpisode->channel() ); if( sqlChannel.isNull() ) { error() << "sqlChannelPtr is NULL after download"; return 0; } QDir dir( sqlChannel->saveLocation().toLocalFile() ); dir.mkpath( "." ); // ensure that the path is there //TODO: what if result is false? - KUrl localUrl = KUrl::fromPath( dir.absolutePath() ); + QUrl localUrl = QUrl::fromLocalFile( dir.absolutePath() ); QString tempName; if( !sqlEpisode->guid().isEmpty() ) tempName = QUrl::toPercentEncoding( sqlEpisode->guid() ); else tempName = QUrl::toPercentEncoding( sqlEpisode->uidUrl() ); QString tempNameMd5( KMD5( tempName.toUtf8() ).hexDigest() ); - localUrl.addPath( tempNameMd5 + PODCAST_TMP_POSTFIX ); + localUrl = localUrl.adjusted(QUrl::StripTrailingSlash); + localUrl.setPath(localUrl.path() + '/' + ( tempNameMd5 + PODCAST_TMP_POSTFIX )); return new QFile( localUrl.toLocalFile() ); } bool SqlPodcastProvider::checkEnclosureLocallyAvailable( KIO::Job *job ) { struct PodcastEpisodeDownload download = m_downloadJobMap.value( job ); Podcasts::SqlPodcastEpisodePtr sqlEpisode = download.episode; if( sqlEpisode.isNull() ) { error() << "sqlEpisodePtr is NULL after download"; return false; } Podcasts::SqlPodcastChannelPtr sqlChannel = Podcasts::SqlPodcastChannelPtr::dynamicCast( sqlEpisode->channel() ); if( sqlChannel.isNull() ) { error() << "sqlChannelPtr is NULL after download"; return false; } - QString fileName = sqlChannel->saveLocation().toLocalFile( KUrl::AddTrailingSlash ); + QString fileName = sqlChannel->saveLocation().toLocalFile( QUrl::AddTrailingSlash ); fileName += download.fileName; debug() << "checking " << fileName; QFileInfo fileInfo( fileName ); if( !fileInfo.exists() ) return false; debug() << fileName << " already exists, no need to redownload"; // NOTE: we need to emit because the KJobProgressBar relies on it to clean up job->kill( KJob::EmitResult ); sqlEpisode->setLocalUrl( fileName ); //TODO: repaint icons, probably with signal metadataUpdate() return true; } void SqlPodcastProvider::addData( KIO::Job *job, const QByteArray &data ) { if( !data.size() ) { return; // EOF } struct PodcastEpisodeDownload &download = m_downloadJobMap[job]; // NOTE: if there is a tmpfile we are already downloading, no need to // checkEnclosureLocallyAvailable() on every data chunk. performance optimization. if( !download.finalNameReady ) { download.finalNameReady = true; if( checkEnclosureLocallyAvailable( job ) ) return; } if( download.tmpFile->write( data ) == -1 ) { error() << "write error for " << download.tmpFile->fileName() << ": " << download.tmpFile->errorString(); job->kill(); } } void SqlPodcastProvider::deleteDownloadedEpisode( Podcasts::PodcastEpisodePtr episode ) { deleteDownloadedEpisode( SqlPodcastEpisodePtr::dynamicCast( episode ) ); } void SqlPodcastProvider::slotStatusBarSorryMessage( const QString &message ) { Amarok::Components::logger()->longMessage( message, Amarok::Logger::Error ); } void SqlPodcastProvider::downloadResult( KJob *job ) { struct PodcastEpisodeDownload download = m_downloadJobMap.value( job ); QFile *tmpFile = download.tmpFile; bool downloadFailed = false; if( job->error() ) { // NOTE: prevents empty error notifications from popping up // in the statusbar when the user cancels a download if( job->error() != KJob::KilledJobError ) { Amarok::Components::logger()->longMessage( job->errorText() ); } error() << "Unable to retrieve podcast media. KIO Error: " << job->errorText(); error() << "keeping temporary file for download restart"; downloadFailed = false; } else { Podcasts::SqlPodcastEpisodePtr sqlEpisode = download.episode; if( sqlEpisode.isNull() ) { error() << "sqlEpisodePtr is NULL after download"; cleanupDownload( job, true ); return; } Podcasts::SqlPodcastChannelPtr sqlChannel = Podcasts::SqlPodcastChannelPtr::dynamicCast( sqlEpisode->channel() ); if( sqlChannel.isNull() ) { error() << "sqlChannelPtr is NULL after download"; cleanupDownload( job, true ); return; } Amarok::QStringx filenameLayout = Amarok::QStringx( sqlChannel->filenameLayout() ); QMap layoutmap; QString sequenceNumber; if( sqlEpisode->artist() ) layoutmap.insert( "artist", sqlEpisode->artist()->prettyName() ); layoutmap.insert( "title", sqlEpisode->title() ); if( sqlEpisode->genre() ) layoutmap.insert( "genre", sqlEpisode->genre()->prettyName() ); if( sqlEpisode->year() ) layoutmap.insert( "year", sqlEpisode->year()->prettyName() ); if( sqlEpisode->composer() ) layoutmap.insert( "composer", sqlEpisode->composer()->prettyName() ); layoutmap.insert( "pubdate", sqlEpisode->pubDate().toString() ); sequenceNumber.sprintf( "%.6d", sqlEpisode->sequenceNumber() ); layoutmap.insert( "number", sequenceNumber ); if( sqlEpisode->album() ) layoutmap.insert( "album", sqlEpisode->album()->prettyName() ); if( !filenameLayout.isEmpty() && Amarok::QStringx::compare( filenameLayout, "%default%", Qt::CaseInsensitive ) ) { filenameLayout = filenameLayout.namedArgs( layoutmap ); //add the file extension to the filename filenameLayout.append( QString( "." ) ); filenameLayout.append( sqlEpisode->type() ); download.fileName = QString( filenameLayout ); } - QString finalName = sqlChannel->saveLocation().toLocalFile( KUrl::AddTrailingSlash ) + QString finalName = sqlChannel->saveLocation().toLocalFile( QUrl::AddTrailingSlash ) + download.fileName; if( tmpFile->rename( finalName ) ) { debug() << "successfully written Podcast Episode " << sqlEpisode->title() << " to " << finalName; sqlEpisode->setLocalUrl( finalName ); if( sqlChannel->writeTags() ) sqlEpisode->writeTagsToFile(); //TODO: force a redraw of the view so the icon can be updated in the PlaylistBrowser emit episodeDownloaded( Podcasts::PodcastEpisodePtr::dynamicCast( sqlEpisode ) ); } else { Amarok::Components::logger()->longMessage( i18n( "Unable to save podcast episode file to %1", finalName ) ); downloadFailed = true; } } //remove it from the jobmap m_completedDownloads++; cleanupDownload( job, downloadFailed ); //start a new download. We just finished one so there is at least one slot free. if( !m_downloadQueue.isEmpty() ) downloadEpisode( m_downloadQueue.takeFirst() ); } void -SqlPodcastProvider::redirected( KIO::Job *job, const KUrl &redirectedUrl ) +SqlPodcastProvider::redirected( KIO::Job *job, const QUrl &redirectedUrl ) { debug() << "redirecting to " << redirectedUrl << ". filename: " << redirectedUrl.fileName(); m_downloadJobMap[job].fileName = redirectedUrl.fileName(); } void SqlPodcastProvider::createTables() const { SqlStorage *sqlStorage = StorageManager::instance()->sqlStorage(); if( !sqlStorage ) return; sqlStorage->query( QString( "CREATE TABLE podcastchannels (" "id " + sqlStorage->idType() + ",url " + sqlStorage->longTextColumnType() + ",title " + sqlStorage->longTextColumnType() + ",weblink " + sqlStorage->longTextColumnType() + ",image " + sqlStorage->longTextColumnType() + ",description " + sqlStorage->longTextColumnType() + ",copyright " + sqlStorage->textColumnType() + ",directory " + sqlStorage->textColumnType() + ",labels " + sqlStorage->textColumnType() + ",subscribedate " + sqlStorage->textColumnType() + ",autoscan BOOL, fetchtype INTEGER" ",haspurge BOOL, purgecount INTEGER" ",writetags BOOL, filenamelayout VARCHAR(1024) ) ENGINE = MyISAM;" ) ); sqlStorage->query( QString( "CREATE TABLE podcastepisodes (" "id " + sqlStorage->idType() + ",url " + sqlStorage->longTextColumnType() + ",channel INTEGER" ",localurl " + sqlStorage->longTextColumnType() + ",guid " + sqlStorage->exactTextColumnType() + ",title " + sqlStorage->longTextColumnType() + ",subtitle " + sqlStorage->longTextColumnType() + ",sequencenumber INTEGER" + ",description " + sqlStorage->longTextColumnType() + ",mimetype " + sqlStorage->textColumnType() + ",pubdate " + sqlStorage->textColumnType() + ",duration INTEGER" ",filesize INTEGER" ",isnew BOOL" ",iskeep BOOL) ENGINE = MyISAM;" ) ); sqlStorage->query( "CREATE FULLTEXT INDEX url_podchannel ON podcastchannels( url );" ); sqlStorage->query( "CREATE FULLTEXT INDEX url_podepisode ON podcastepisodes( url );" ); sqlStorage->query( "CREATE FULLTEXT INDEX localurl_podepisode ON podcastepisodes( localurl );" ); } void SqlPodcastProvider::updateDatabase( int fromVersion, int toVersion ) { debug() << QString( "Updating Podcast tables from version %1 to version %2" ) .arg( fromVersion ).arg( toVersion ); SqlStorage *sqlStorage = StorageManager::instance()->sqlStorage(); if( !sqlStorage ) return; #define escape(x) sqlStorage->escape(x) if( fromVersion == 1 && toVersion == 2 ) { QString updateChannelQuery = QString( "ALTER TABLE podcastchannels" " ADD subscribedate " + sqlStorage->textColumnType() + ';' ); sqlStorage->query( updateChannelQuery ); QString setDateQuery = QString( "UPDATE podcastchannels SET subscribedate='%1' WHERE subscribedate='';" ) .arg( escape( QDate::currentDate().toString() ) ); sqlStorage->query( setDateQuery ); } else if( fromVersion < 3 && toVersion == 3 ) { sqlStorage->query( QString( "CREATE TABLE podcastchannels_temp (" "id " + sqlStorage->idType() + ",url " + sqlStorage->exactTextColumnType() + " UNIQUE" ",title " + sqlStorage->textColumnType() + ",weblink " + sqlStorage->exactTextColumnType() + ",image " + sqlStorage->exactTextColumnType() + ",description " + sqlStorage->longTextColumnType() + ",copyright " + sqlStorage->textColumnType() + ",directory " + sqlStorage->textColumnType() + ",labels " + sqlStorage->textColumnType() + ",subscribedate " + sqlStorage->textColumnType() + ",autoscan BOOL, fetchtype INTEGER" ",haspurge BOOL, purgecount INTEGER ) ENGINE = MyISAM;" ) ); sqlStorage->query( QString( "CREATE TABLE podcastepisodes_temp (" "id " + sqlStorage->idType() + ",url " + sqlStorage->exactTextColumnType() + " UNIQUE" ",channel INTEGER" ",localurl " + sqlStorage->exactTextColumnType() + ",guid " + sqlStorage->exactTextColumnType() + ",title " + sqlStorage->textColumnType() + ",subtitle " + sqlStorage->textColumnType() + ",sequencenumber INTEGER" + ",description " + sqlStorage->longTextColumnType() + ",mimetype " + sqlStorage->textColumnType() + ",pubdate " + sqlStorage->textColumnType() + ",duration INTEGER" ",filesize INTEGER" ",isnew BOOL" ",iskeep BOOL) ENGINE = MyISAM;" ) ); sqlStorage->query( "INSERT INTO podcastchannels_temp SELECT * FROM podcastchannels;" ); sqlStorage->query( "INSERT INTO podcastepisodes_temp SELECT * FROM podcastepisodes;" ); sqlStorage->query( "DROP TABLE podcastchannels;" ); sqlStorage->query( "DROP TABLE podcastepisodes;" ); createTables(); sqlStorage->query( "INSERT INTO podcastchannels SELECT * FROM podcastchannels_temp;" ); sqlStorage->query( "INSERT INTO podcastepisodes SELECT * FROM podcastepisodes_temp;" ); sqlStorage->query( "DROP TABLE podcastchannels_temp;" ); sqlStorage->query( "DROP TABLE podcastepisodes_temp;" ); } if( fromVersion < 4 && toVersion == 4 ) { QString updateChannelQuery = QString( "ALTER TABLE podcastchannels" " ADD writetags BOOL;" ); sqlStorage->query( updateChannelQuery ); QString setWriteTagsQuery = QString( "UPDATE podcastchannels SET writetags=" + sqlStorage->boolTrue() + " WHERE 1;" ); sqlStorage->query( setWriteTagsQuery ); } if( fromVersion < 5 && toVersion == 5 ) { QString updateChannelQuery = QString ( "ALTER TABLE podcastchannels" " ADD filenamelayout VARCHAR(1024);" ); sqlStorage->query( updateChannelQuery ); QString setWriteTagsQuery = QString( "UPDATE podcastchannels SET filenamelayout='%default%'" ); sqlStorage->query( setWriteTagsQuery ); } if( fromVersion < 6 && toVersion == 6 ) { QString updateEpisodeQuery = QString ( "ALTER TABLE podcastepisodes" " ADD iskeep BOOL;" ); sqlStorage->query( updateEpisodeQuery ); QString setIsKeepQuery = QString( "UPDATE podcastepisodes SET iskeep=FALSE;" ); sqlStorage->query( setIsKeepQuery ); } QString updateAdmin = QString( "UPDATE admin SET version=%1 WHERE component='%2';" ); sqlStorage->query( updateAdmin.arg( toVersion ).arg( escape( key ) ) ); loadPodcasts(); } void SqlPodcastProvider::fetchImage( SqlPodcastChannelPtr channel ) { if( m_podcastImageFetcher == 0 ) { m_podcastImageFetcher = new PodcastImageFetcher(); connect( m_podcastImageFetcher, SIGNAL(imageReady(Podcasts::PodcastChannelPtr,QImage)), SLOT(channelImageReady(Podcasts::PodcastChannelPtr,QImage)) ); connect( m_podcastImageFetcher, SIGNAL(done(PodcastImageFetcher*)), SLOT(podcastImageFetcherDone(PodcastImageFetcher*)) ); } m_podcastImageFetcher->addChannel( PodcastChannelPtr::dynamicCast( channel ) ); } void SqlPodcastProvider::channelImageReady( Podcasts::PodcastChannelPtr channel, QImage image ) { if( image.isNull() ) return; channel->setImage( image ); } void SqlPodcastProvider::podcastImageFetcherDone( PodcastImageFetcher *fetcher ) { fetcher->deleteLater(); m_podcastImageFetcher = 0; } void SqlPodcastProvider::slotConfigureProvider() { configureProvider(); } diff --git a/src/core-impl/podcasts/sql/SqlPodcastProvider.h b/src/core-impl/podcasts/sql/SqlPodcastProvider.h index 606d990f97..ef354ceaba 100644 --- a/src/core-impl/podcasts/sql/SqlPodcastProvider.h +++ b/src/core-impl/podcasts/sql/SqlPodcastProvider.h @@ -1,205 +1,205 @@ /**************************************************************************************** * Copyright (c) 2007 Bart Cerneels * * * * 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, see . * ****************************************************************************************/ #ifndef SQLPODCASTPROVIDER_H #define SQLPODCASTPROVIDER_H #include "core/podcasts/PodcastProvider.h" #include "core/podcasts/PodcastReader.h" #include "SqlPodcastMeta.h" #include #include class PodcastImageFetcher; class KDialog; -class KUrl; +class QUrl; class PodcastReader; class SqlStorage; class QTimer; namespace Ui { class SqlPodcastProviderSettingsWidget; } namespace Podcasts { /** @author Bart Cerneels */ class AMAROK_EXPORT SqlPodcastProvider : public Podcasts::PodcastProvider { Q_OBJECT public: SqlPodcastProvider(); virtual ~SqlPodcastProvider(); //TrackProvider methods - virtual bool possiblyContainsTrack( const KUrl &url ) const; - virtual Meta::TrackPtr trackForUrl( const KUrl &url ); + virtual bool possiblyContainsTrack( const QUrl &url ) const; + virtual Meta::TrackPtr trackForUrl( const QUrl &url ); //PlaylistProvider methods virtual QString prettyName() const { return i18n("Local Podcasts"); } virtual KIcon icon() const { return KIcon( "server-database" ); } virtual Playlists::PlaylistList playlists(); //PlaylistProvider methods virtual QActionList providerActions(); virtual QActionList playlistActions( const Playlists::PlaylistList &playlists ); virtual QActionList trackActions( const QMultiHash &playlistTracks ); //PodcastProvider methods virtual Podcasts::PodcastEpisodePtr episodeForGuid( const QString &guid ); - virtual void addPodcast( const KUrl &url ); + virtual void addPodcast( const QUrl &url ); virtual Podcasts::PodcastChannelPtr addChannel( Podcasts::PodcastChannelPtr channel ); virtual Podcasts::PodcastEpisodePtr addEpisode( Podcasts::PodcastEpisodePtr episode ); virtual Podcasts::PodcastChannelList channels(); virtual void completePodcastDownloads(); //SqlPodcastProvider specific methods virtual Podcasts::SqlPodcastChannelPtr podcastChannelForId( int podcastChannelDbId ); - virtual KUrl baseDownloadDir() const { return m_baseDownloadDir; } + virtual QUrl baseDownloadDir() const { return m_baseDownloadDir; } public slots: void updateAll(); void downloadEpisode( Podcasts::PodcastEpisodePtr episode ); void deleteDownloadedEpisode( Podcasts::PodcastEpisodePtr episode ); void slotReadResult( PodcastReader *podcastReader ); void downloadEpisode( Podcasts::SqlPodcastEpisodePtr episode ); void deleteDownloadedEpisode( Podcasts::SqlPodcastEpisodePtr episode ); private slots: void downloadResult( KJob * ); void addData( KIO::Job *job, const QByteArray & data ); - void redirected( KIO::Job *, const KUrl& ); + void redirected( KIO::Job *, const QUrl& ); void autoUpdate(); void slotDeleteDownloadedEpisodes(); void slotDownloadEpisodes(); void slotSetKeep(); void slotConfigureChannel(); void slotRemoveChannels(); void slotUpdateChannels(); void slotDownloadProgress( KJob *job, unsigned long percent ); void slotWriteTagsToFiles(); void slotConfigChanged(); void slotExportOpml(); signals: void totalPodcastDownloadProgress( int progress ); //SqlPodcastProvider signals void episodeDownloaded( Podcasts::PodcastEpisodePtr ); void episodeDeleted( Podcasts::PodcastEpisodePtr ); private slots: void channelImageReady( Podcasts::PodcastChannelPtr, QImage ); void podcastImageFetcherDone( PodcastImageFetcher * ); void slotConfigureProvider(); void slotStatusBarNewProgressOperation( KIO::TransferJob * job, const QString &description, Podcasts::PodcastReader* reader ); void slotStatusBarSorryMessage( const QString &message ); void slotOpmlWriterDone( int result ); private: void startTimer(); void configureProvider(); void configureChannel( Podcasts::SqlPodcastChannelPtr channel ); void updateSqlChannel( Podcasts::SqlPodcastChannelPtr channel ); /** creates all the necessary tables, indexes etc. for the database */ void createTables() const; void loadPodcasts(); /** @arg string: a url, localUrl or guid in string form */ Podcasts::SqlPodcastEpisodePtr sqlEpisodeForString( const QString &string ); void updateDatabase( int fromVersion, int toVersion ); void fetchImage( Podcasts::SqlPodcastChannelPtr channel ); /** shows a modal dialog asking the user if he really wants to unsubscribe and if he wants to keep the podcast media */ QPair confirmUnsubscribe( Podcasts::SqlPodcastChannelPtr channel ); /** remove the episodes in the list from the filesystem */ void deleteDownloadedEpisodes( Podcasts::SqlPodcastEpisodeList &episodes ); void moveDownloadedEpisodes( Podcasts::SqlPodcastChannelPtr channel ); /** Removes a podcast from the list. Will ask for confirmation to delete the episodes * as well */ void removeSubscription( Podcasts::SqlPodcastChannelPtr channel ); - void subscribe( const KUrl &url ); + void subscribe( const QUrl &url ); QFile* createTmpFile ( Podcasts::SqlPodcastEpisodePtr sqlEpisode ); void cleanupDownload( KJob *job, bool downloadFailed ); /** returns true if the file that is downloaded by 'job' is already locally available */ bool checkEnclosureLocallyAvailable( KIO::Job *job ); Podcasts::SqlPodcastChannelList m_channels; QTimer *m_updateTimer; int m_autoUpdateInterval; //interval between autoupdate attempts in minutes unsigned int m_updatingChannels; unsigned int m_maxConcurrentUpdates; Podcasts::SqlPodcastChannelList m_updateQueue; - QList m_subscribeQueue; + QList m_subscribeQueue; struct PodcastEpisodeDownload { Podcasts::SqlPodcastEpisodePtr episode; QFile *tmpFile; QString fileName; bool finalNameReady; }; QHash m_downloadJobMap; Podcasts::SqlPodcastEpisodeList m_downloadQueue; int m_maxConcurrentDownloads; int m_completedDownloads; - KUrl m_baseDownloadDir; + QUrl m_baseDownloadDir; KDialog *m_providerSettingsDialog; Ui::SqlPodcastProviderSettingsWidget *m_providerSettingsWidget; QList m_providerActions; QAction *m_configureChannelAction; //Configure a Channel QAction *m_deleteAction; //delete a downloaded Episode QAction *m_downloadAction; QAction *m_keepAction; QAction *m_removeAction; //remove a subscription QAction *m_updateAction; QAction *m_writeTagsAction; //write feed information to downloaded file PodcastImageFetcher *m_podcastImageFetcher; }; } //namespace Podcasts #endif diff --git a/src/core-impl/support/TrackLoader.cpp b/src/core-impl/support/TrackLoader.cpp index 04bb0a8494..d8b4dde2f2 100644 --- a/src/core-impl/support/TrackLoader.cpp +++ b/src/core-impl/support/TrackLoader.cpp @@ -1,294 +1,294 @@ /**************************************************************************************** * Copyright (c) 2007-2008 Ian Monroe * * Copyright (c) 2013 MatÄ›j Laitl * * * * 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) version 3 or * * any later version accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * 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, see . * ****************************************************************************************/ #include "TrackLoader.h" #include "core/playlists/PlaylistFormat.h" #include "core/support/Debug.h" #include "core-impl/meta/file/File.h" #include "core-impl/meta/proxy/MetaProxy.h" #include "core-impl/meta/multi/MultiTrack.h" #include "core-impl/playlists/types/file/PlaylistFileSupport.h" #include #include #include #include TrackLoader::TrackLoader( Flags flags, int timeout ) : m_status( LoadingTracks ) , m_flags( flags ) , m_timeout( timeout ) { } TrackLoader::~TrackLoader() { } void -TrackLoader::init( const KUrl &url ) +TrackLoader::init( const QUrl &url ) { - init( QList() << url ); + init( QList() << url ); } void TrackLoader::init( const QList &qurls ) { - QList kurls; + QList kurls; foreach( const QUrl &qurl, qurls ) - kurls << KUrl( qurl ); + kurls << QUrl( qurl ); init( kurls ); } void -TrackLoader::init( const QList &urls ) +TrackLoader::init( const QList &urls ) { m_sourceUrls = urls; QTimer::singleShot( 0, this, SLOT(processNextSourceUrl()) ); } void TrackLoader::init( const Playlists::PlaylistList &playlists ) { m_resultPlaylists = playlists; // no need to process source urls here, short-cut to result urls (just playlists) QTimer::singleShot( 0, this, SLOT(processNextResultUrl()) ); } void TrackLoader::processNextSourceUrl() { if( m_sourceUrls.isEmpty() ) { QTimer::singleShot( 0, this, SLOT(processNextResultUrl()) ); return; } - KUrl sourceUrl = m_sourceUrls.takeFirst(); + QUrl sourceUrl = m_sourceUrls.takeFirst(); if( sourceUrl.isLocalFile() && QFileInfo( sourceUrl.toLocalFile() ).isDir() ) { // KJobs delete themselves KIO::ListJob *lister = KIO::listRecursive( sourceUrl, KIO::HideProgressInfo ); connect( lister, SIGNAL(finished(KJob*)), SLOT(listJobFinished()) ); connect( lister, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), SLOT(directoryListResults(KIO::Job*,KIO::UDSEntryList)) ); // listJobFinished() calls processNextSourceUrl() in the end, don't do it here: return; } else m_resultUrls.append( sourceUrl ); QTimer::singleShot( 0, this, SLOT(processNextSourceUrl()) ); } void TrackLoader::directoryListResults( KIO::Job *job, const KIO::UDSEntryList &list ) { //dfaure says that job->redirectionUrl().isValid() ? job->redirectionUrl() : job->url(); might be needed //but to wait until an issue is actually found, since it might take more work - const KUrl dir = static_cast( job )->url(); + const QUrl dir = static_cast( job )->url(); foreach( const KIO::UDSEntry &entry, list ) { KFileItem item( entry, dir, true, true ); - KUrl url = item.url(); + QUrl url = item.url(); if( MetaFile::Track::isTrack( url ) ) m_listJobResults << url; } } void TrackLoader::listJobFinished() { qSort( m_listJobResults.begin(), m_listJobResults.end(), directorySensitiveLessThan ); m_resultUrls << m_listJobResults; m_listJobResults.clear(); QTimer::singleShot( 0, this, SLOT(processNextSourceUrl()) ); } void TrackLoader::processNextResultUrl() { using namespace Playlists; if( !m_resultPlaylists.isEmpty() ) { PlaylistPtr playlist = m_resultPlaylists.takeFirst(); PlaylistObserver::subscribeTo( playlist ); playlist->triggerTrackLoad(); // playlist track loading is on demand. // will trigger tracksLoaded() which in turn calls processNextResultUrl(), // therefore we shouldn't call trigger processNextResultUrl() here: return; } if( m_resultUrls.isEmpty() ) { mayFinish(); return; } - KUrl resultUrl = m_resultUrls.takeFirst(); + QUrl resultUrl = m_resultUrls.takeFirst(); if( isPlaylist( resultUrl ) ) { PlaylistFilePtr playlist = loadPlaylistFile( resultUrl ); if( playlist ) { PlaylistObserver::subscribeTo( PlaylistPtr::staticCast( playlist ) ); playlist->triggerTrackLoad(); // playlist track loading is on demand. // will trigger tracksLoaded() which in turn calls processNextResultUrl(), // therefore we shouldn't call trigger processNextResultUrl() here: return; } else warning() << __PRETTY_FUNCTION__ << "cannot load playlist" << resultUrl; } else if( MetaFile::Track::isTrack( resultUrl ) ) { MetaProxy::TrackPtr proxyTrack( new MetaProxy::Track( resultUrl ) ); proxyTrack->setTitle( resultUrl.fileName() ); // set temporary name Meta::TrackPtr track( proxyTrack.data() ); m_tracks << Meta::TrackPtr( track ); if( m_flags.testFlag( FullMetadataRequired ) && !proxyTrack->isResolved() ) { m_unresolvedTracks.insert( track ); Observer::subscribeTo( track ); } } else warning() << __PRETTY_FUNCTION__ << resultUrl << "is neither a playlist or a track, skipping"; QTimer::singleShot( 0, this, SLOT(processNextResultUrl()) ); } void TrackLoader::tracksLoaded( Playlists::PlaylistPtr playlist ) { // this method needs to be thread-safe! // some playlists used to emit tracksLoaded() in ->tracks(), prevent infinite // recursion by unsubscribing early PlaylistObserver::unsubscribeFrom( playlist ); // accessing m_tracks is thread-safe as nothing else is happening in this class in // the main thread while we are waiting for tracksLoaded() to trigger: Meta::TrackList tracks = playlist->tracks(); if( m_flags.testFlag( FullMetadataRequired ) ) { foreach( const Meta::TrackPtr &track, tracks ) { MetaProxy::TrackPtr proxyTrack = MetaProxy::TrackPtr::dynamicCast( track ); if( !proxyTrack ) { debug() << __PRETTY_FUNCTION__ << "strange, playlist" << playlist->name() << "doesn't use MetaProxy::Tracks"; continue; } if( !proxyTrack->isResolved() ) { m_unresolvedTracks.insert( track ); Observer::subscribeTo( track ); } } } static const QSet remoteProtocols = QSet() << "http" << "https" << "mms" << "smb"; // consider unifying with CollectionManager::trackForUrl() if( m_flags.testFlag( RemotePlaylistsAreStreams ) && tracks.count() > 1 && remoteProtocols.contains( playlist->uidUrl().protocol() ) ) { m_tracks << Meta::TrackPtr( new Meta::MultiTrack( playlist ) ); } else m_tracks << tracks; // this also ensures that processNextResultUrl() will resume in the main thread QTimer::singleShot( 0, this, SLOT(processNextResultUrl()) ); } void TrackLoader::metadataChanged( Meta::TrackPtr track ) { // first metadataChanged() from a MetaProxy::Track means that it has found the real track bool isEmpty; { QMutexLocker locker( &m_unresolvedTracksMutex ); m_unresolvedTracks.remove( track ); isEmpty = m_unresolvedTracks.isEmpty(); } Observer::unsubscribeFrom( track ); if( m_status == MayFinish && isEmpty ) QTimer::singleShot( 0, this, SLOT(finish()) ); } void TrackLoader::mayFinish() { m_status = MayFinish; bool isEmpty; { QMutexLocker locker( &m_unresolvedTracksMutex ); isEmpty = m_unresolvedTracks.isEmpty(); } if( isEmpty ) { finish(); return; } // we must wait for tracks to resolve, but with a timeout QTimer::singleShot( m_timeout, this, SLOT(finish()) ); } void TrackLoader::finish() { // prevent double emit of finished(), race between singleshot QTimers from mayFinish() // and metadataChanged() if( m_status != MayFinish ) return; m_status = Finished; emit finished( m_tracks ); deleteLater(); } bool -TrackLoader::directorySensitiveLessThan( const KUrl &left, const KUrl &right ) +TrackLoader::directorySensitiveLessThan( const QUrl &left, const QUrl &right ) { - QString leftDir = left.directory( KUrl::AppendTrailingSlash ); - QString rightDir = right.directory( KUrl::AppendTrailingSlash ); + QString leftDir = left.directory( QUrl::AppendTrailingSlash ); + QString rightDir = right.directory( QUrl::AppendTrailingSlash ); // filter out tracks from same directories: if( leftDir == rightDir ) return QString::localeAwareCompare( left.fileName(), right.fileName() ) < 0; // left is "/a/b/c/", right is "/a/b/" if( leftDir.startsWith( rightDir ) ) return true; // we sort directories above files // left is "/a/b/", right is "/a/b/c/" if( rightDir.startsWith( leftDir ) ) return false; return QString::localeAwareCompare( leftDir, rightDir ) < 0; } diff --git a/src/core-impl/support/TrackLoader.h b/src/core-impl/support/TrackLoader.h index 1a368f4b09..02ec8e865e 100644 --- a/src/core-impl/support/TrackLoader.h +++ b/src/core-impl/support/TrackLoader.h @@ -1,152 +1,152 @@ /**************************************************************************************** * Copyright (c) 2008 Ian Monroe * * Copyright (c) 2013 MatÄ›j Laitl * * * * 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) version 3 or * * any later version accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * 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, see . * ****************************************************************************************/ #ifndef AMAROK_TRACKLOADER_H #define AMAROK_TRACKLOADER_H #include "amarok_export.h" #include "core/meta/forward_declarations.h" #include "core/meta/Observer.h" #include "core/playlists/Playlist.h" namespace KIO { class Job; class UDSEntry; typedef QList UDSEntryList; } /** * Helper class that helps with loading of urls (with local and remote tracks, * playlists and local directories) to tracks. * * Only explicitly listed playlists are loaded, not the ones found in subdirectories. * TrackLoader takes care to preserve order of urls you pass, and it sorts tracks in * directories you pass it using directory- and locale-aware sort. */ class AMAROK_EXPORT TrackLoader : public QObject, public Playlists::PlaylistObserver, public Meta::Observer { Q_OBJECT public: /** * FullMetadataRequired: signal TrackLoader that it should postpone the finished() * signal until the any possible proxy tracks have resolved and their full * metadata is available. Also use this flag when you need to immediately play * the tracks. This no longer implies any blocking behaviour, you'll just get the * finished signal a bit later. * * RemotePlaylistsAreStreams: treat playlists with remote urls as Streams with * multiple alternative download locations (Meta::MultiTracks). Works even when * you pass playlists. */ enum Flag { FullMetadataRequired = 1 << 0, RemotePlaylistsAreStreams = 1 << 1, }; Q_DECLARE_FLAGS( Flags, Flag ) /** * Construct TrackLoader. You must construct it on the heap, it will auto-delete * itself. * * @param flags binary or of flags, see TrackLoader::Flags enum * @param timeout if FullMetadataRequired is in flags, this is the timeout in * milliseconds for wating on track to resolve. Ignored otherwise. */ TrackLoader( Flags flags = 0, int timeout = 2000 ); ~TrackLoader(); /** - * Convenience overload for init( const QList &urls ) + * Convenience overload for init( const QList &urls ) */ - void init( const KUrl &url ); + void init( const QUrl &url ); /** - * Convenience overload for init( const QList &urls ) + * Convenience overload for init( const QList &urls ) */ void init( const QList &urls ); /** * Starts TrackLoader's job, you'll get finished() signal in the end and * TrackLoader will auto-delete itself. * * @urls list of urls to load tracks from, you can pass local and remote urls * pointing to directories, tracks and playlists. */ - void init( const QList &urls ); + void init( const QList &urls ); /** * Short-hand if you already have a list of playlists and want a convenient way - * to get notified of their loaded tracks. See init( const QList ) and + * to get notified of their loaded tracks. See init( const QList ) and * class description. */ void init( const Playlists::PlaylistList &playlists ); /* PlaylistObserver methods */ using PlaylistObserver::metadataChanged; virtual void tracksLoaded( Playlists::PlaylistPtr playlist ); /* Meta::Observer methods */ using Observer::metadataChanged; virtual void metadataChanged( Meta::TrackPtr track ); signals: void finished( const Meta::TrackList &tracks ); private slots: void processNextSourceUrl(); void directoryListResults( KIO::Job *job, const KIO::UDSEntryList &list ); void listJobFinished(); void processNextResultUrl(); /** * Emits the result and auto-destroys the TrackLoader */ void finish(); private: enum Status { LoadingTracks, MayFinish, Finished }; void mayFinish(); - static bool directorySensitiveLessThan( const KUrl &left, const KUrl &right ); + static bool directorySensitiveLessThan( const QUrl &left, const QUrl &right ); Status m_status; const Flags m_flags; int m_timeout; /// passed urls, may contain urls of directories - QList m_sourceUrls; + QList m_sourceUrls; /// contains just urls of tracks and playlists - QList m_resultUrls; + QList m_resultUrls; /// a list of playlists directly passed, same semantics as m_resultUrls Playlists::PlaylistList m_resultPlaylists; /// the tracks found Meta::TrackList m_tracks; /// temporary list of results of the list job, to keep right sorting - QList m_listJobResults; + QList m_listJobResults; /// set of unresolved MetaProxy::Tracks that we wait for QSet m_unresolvedTracks; QMutex m_unresolvedTracksMutex; }; Q_DECLARE_OPERATORS_FOR_FLAGS( TrackLoader::Flags ) #endif // AMAROK_TRACKLOADER_H diff --git a/src/core/capabilities/MultiPlayableCapability.h b/src/core/capabilities/MultiPlayableCapability.h index af1fe91eca..20f5736733 100644 --- a/src/core/capabilities/MultiPlayableCapability.h +++ b/src/core/capabilities/MultiPlayableCapability.h @@ -1,44 +1,44 @@ /**************************************************************************************** * Copyright (c) 2008 Shane King * * * * 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, see . * ****************************************************************************************/ #ifndef AMAROK_MULTIPLAYABLECAPABILITY_H #define AMAROK_MULTIPLAYABLECAPABILITY_H #include "core/capabilities/Capability.h" -#include +#include namespace Capabilities { class AMAROK_CORE_EXPORT MultiPlayableCapability : public Capability { Q_OBJECT public: virtual ~MultiPlayableCapability(); static Type capabilityInterfaceType() { return Capabilities::Capability::MultiPlayable; } virtual void fetchFirst() = 0; virtual void fetchNext() = 0; signals: - void playableUrlFetched( const KUrl &url ); + void playableUrlFetched( const QUrl &url ); }; } #endif // AMAROK_MULTIPLAYABLECAPABILITY_H diff --git a/src/core/capabilities/MultiSourceCapability.h b/src/core/capabilities/MultiSourceCapability.h index 7c7377537e..f211e78fcd 100644 --- a/src/core/capabilities/MultiSourceCapability.h +++ b/src/core/capabilities/MultiSourceCapability.h @@ -1,72 +1,72 @@ /**************************************************************************************** * Copyright (c) 2009 Nikolaj Hald Nielsen * * Copyright (c) 2012 MatÄ›j Laitl * * * * 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, see . * ****************************************************************************************/ #ifndef METAMULTISOURCECAPABILITY_H #define METAMULTISOURCECAPABILITY_H #include "core/capabilities/Capability.h" -class KUrl; +class QUrl; namespace Capabilities { /** * A capability for tracks that can have several different source urls, such as * multiple fallback streams for a radio station. If one source url fails or finishes, * the track will automatically use the next one. It is also possbile to get a list * of all urls that can be presented to the user so tha she can choose. * * @author Nikolaj Hald Nielsen */ class AMAROK_CORE_EXPORT MultiSourceCapability : public Capability { Q_OBJECT public: MultiSourceCapability(); virtual ~MultiSourceCapability(); static Type capabilityInterfaceType() { return MultiSource; } /** * Return list of displayable urls in this MultiSource. Only for display * purposes, don't attempt to play these urls. */ virtual QStringList sources() const = 0; /** * Set current source. Does nothing if @param current is out of bounds. */ virtual void setSource( int source ) = 0; /** * Get index of the current source */ virtual int current() const = 0; /** * Return the url of the next source without actually advancing to it. * Returns empty url if the current source is the last one. */ - virtual KUrl nextUrl() const = 0; + virtual QUrl nextUrl() const = 0; signals: - void urlChanged( const KUrl &url ); + void urlChanged( const QUrl &url ); }; } #endif diff --git a/src/core/collections/Collection.cpp b/src/core/collections/Collection.cpp index 471deb3c73..e90dbc7db0 100644 --- a/src/core/collections/Collection.cpp +++ b/src/core/collections/Collection.cpp @@ -1,93 +1,93 @@ /**************************************************************************************** * Copyright (c) 2007-2008 Maximilian Kossick * * * * 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, see . * ****************************************************************************************/ #include "core/collections/Collection.h" #include "core/collections/CollectionLocation.h" #include "core/meta/Meta.h" Collections::CollectionFactory::CollectionFactory( QObject *parent, const QVariantList &args ) : Plugins::PluginFactory( parent, args ) { } Collections::CollectionFactory::~CollectionFactory() { } Collections::TrackProvider::TrackProvider() { } Collections::TrackProvider::~TrackProvider() { } bool -Collections::TrackProvider::possiblyContainsTrack( const KUrl &url ) const +Collections::TrackProvider::possiblyContainsTrack( const QUrl &url ) const { Q_UNUSED( url ) return false; } Meta::TrackPtr -Collections::TrackProvider::trackForUrl( const KUrl &url ) +Collections::TrackProvider::trackForUrl( const QUrl &url ) { Q_UNUSED( url ) return Meta::TrackPtr(); } // Collection Collections::Collection::~Collection() { } QString Collections::Collection::uidUrlProtocol() const { return QString(); } Collections::CollectionLocation* Collections::Collection::location() { return new Collections::CollectionLocation( this ); } bool Collections::Collection::isWritable() const { Collections::CollectionLocation* loc = const_cast(this)->location(); if( !loc ) return false; bool writable = loc->isWritable(); delete loc; return writable; } bool Collections::Collection::isOrganizable() const { Collections::CollectionLocation* loc = const_cast(this)->location(); if( !loc ) return false; bool organizable = loc->isOrganizable(); delete loc; return organizable; } diff --git a/src/core/collections/Collection.h b/src/core/collections/Collection.h index bfa6e32bcf..61a468f500 100644 --- a/src/core/collections/Collection.h +++ b/src/core/collections/Collection.h @@ -1,185 +1,185 @@ /**************************************************************************************** * Copyright (c) 2007-2008 Maximilian Kossick * * Copyright (c) 2012 MatÄ›j Laitl * * * * 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, see . * ****************************************************************************************/ #ifndef AMAROK_COLLECTION_H #define AMAROK_COLLECTION_H #include "core/amarokcore_export.h" #include "core/interfaces/MetaCapability.h" #include "core/support/PluginFactory.h" #include namespace Meta { class Track; typedef KSharedPtr TrackPtr; } namespace Playlists { class UserPlaylistProvider; } class KIcon; namespace Collections { class Collection; class CollectionLocation; class QueryMaker; typedef QList CollectionList; /** A plugin that creates new collections. */ class AMAROK_CORE_EXPORT CollectionFactory : public Plugins::PluginFactory { Q_OBJECT public: CollectionFactory( QObject *parent, const QVariantList &args ); virtual ~CollectionFactory(); signals: void newCollection( Collections::Collection *newCollection ); }; /** A TrackProvider is a class that can lookup urls and return Track objects. * * A track provider is implemented by every collection, but there * are also a couple of other track providers. * All TrackProvider are managed by the CollectionManager. */ class AMAROK_CORE_EXPORT TrackProvider { public: TrackProvider(); virtual ~TrackProvider(); /** * Returns true if this track provider has a chance of providing the * track specified by @p url. * This should do a minimal amount of checking, and return quickly. */ - virtual bool possiblyContainsTrack( const KUrl &url ) const; + virtual bool possiblyContainsTrack( const QUrl &url ) const; /** * Creates a TrackPtr object for url @p url. Returns a null track Ptr if * it cannot be done. * If asynchronysity is desired it is suggested to return a MetaProxy track here * and have the proxy watch for the real track. */ - virtual Meta::TrackPtr trackForUrl( const KUrl &url ); + virtual Meta::TrackPtr trackForUrl( const QUrl &url ); }; class AMAROK_CORE_EXPORT Collection : public QObject, public TrackProvider, public MetaCapability { Q_OBJECT public: virtual ~Collection(); /** * The collection's querymaker * @return A querymaker that belongs to this collection. */ virtual QueryMaker *queryMaker() = 0; /** * The protocol of uids coming from this collection. * @return A string of the protocol, without the :// */ virtual QString uidUrlProtocol() const; /** * @return A unique identifier for this collection */ virtual QString collectionId() const = 0; /** * @return a user visible name for this collection, to be displayed in the collectionbrowser and elsewhere */ virtual QString prettyName() const = 0; /** * @return an icon representing this collection */ virtual KIcon icon() const = 0; virtual bool hasCapacity() const { return false; } virtual float usedCapacity() const { return 0.0; } virtual float totalCapacity() const { return 0.0; } /** * Create collection location that can be used to copy track to this * collection or to delete collection tracks. If you don't call * prepare{Move,Copy,Remove} on it, you must delete it after use. */ virtual Collections::CollectionLocation *location(); /** * Return true if this collection can be written to (tracks added, removed). * Convenience short-cut for calling CollectionLocation's isWritable. */ virtual bool isWritable() const; /** * Return true if user can choose track file path within this collection. * Convenience short-cut for calling CollectionLocation's isOrganizable. */ virtual bool isOrganizable() const; signals: /** * Once you register a collection with CollectionManager, this signal is the * only way to safely destroy it. CollectionManger will remove this collection * from the list of active ones and will destroy this collection after some * time. */ void remove(); /** * This signal must be emitted when the collection contents has changed * significantly. * * More specifically, you must emit this signal (only) in such situations: * a) the set of entities (tracks, albums, years, ...) in this collection has * changed: a track was added, album is renamed, year was removed... * b) the relationship between the entities has changed: the track changed * album, album is no longer associated to an album artist and bacame a * compilation, an alum changed its year... * * You should not emit this signal when some minor data of an entity change, * for example when a track comment changes, etc. * * Also note there are ::notifyObservers() methods of various entities. * ::notifyObservers() and Collection::updated() are perpendicular and * responsibility to call one of these may and may not mean need to call the * other. * * This signal spedifically this means that previous done searches can no * longer be considered valid. */ void updated(); }; } Q_DECLARE_METATYPE( Collections::Collection* ) Q_DECLARE_METATYPE( Collections::CollectionList ) #define AMAROK_EXPORT_COLLECTION( classname, libname ) \ K_PLUGIN_FACTORY( factory, registerPlugin(); ) \ K_EXPORT_PLUGIN( factory( "amarok_collection-" #libname ) ) #endif /* AMAROK_COLLECTION_H */ diff --git a/src/core/collections/CollectionLocation.cpp b/src/core/collections/CollectionLocation.cpp index f959b31798..38cc78be59 100644 --- a/src/core/collections/CollectionLocation.cpp +++ b/src/core/collections/CollectionLocation.cpp @@ -1,742 +1,742 @@ /**************************************************************************************** * Copyright (c) 2007-2008 Maximilian Kossick * * Copyright (c) 2008 Jason A. Donenfeld * * Copyright (c) 2010 Casey Link * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "CollectionLocation" #include "CollectionLocation.h" #include "core/capabilities/TranscodeCapability.h" #include "core/collections/Collection.h" #include "core/collections/CollectionLocationDelegate.h" #include "core/collections/QueryMaker.h" #include "core/meta/Meta.h" #include "core/support/Components.h" #include "core/support/Debug.h" #include "core/transcoding/TranscodingConfiguration.h" #include "core/transcoding/TranscodingController.h" #include #include using namespace Collections; CollectionLocation::CollectionLocation() :QObject() , m_destination( 0 ) , m_source( 0 ) , m_sourceTracks() , m_parentCollection( 0 ) , m_removeSources( false ) , m_isRemoveAction( false ) , m_noRemoveConfirmation( false ) , m_transcodingConfiguration( Transcoding::JUST_COPY ) { //nothing to do } CollectionLocation::CollectionLocation( Collections::Collection *parentCollection) :QObject() , m_destination( 0 ) , m_source( 0 ) , m_sourceTracks() , m_parentCollection( parentCollection ) , m_removeSources( false ) , m_isRemoveAction( false ) , m_noRemoveConfirmation( false ) , m_transcodingConfiguration( Transcoding::JUST_COPY ) { //nothing to do } CollectionLocation::~CollectionLocation() { //nothing to do } Collections::Collection* CollectionLocation::collection() const { return m_parentCollection; } QString CollectionLocation::prettyLocation() const { return QString(); } QStringList CollectionLocation::actualLocation() const { return QStringList(); } bool CollectionLocation::isWritable() const { return false; } bool CollectionLocation::isOrganizable() const { return false; } void CollectionLocation::prepareCopy( Meta::TrackPtr track, CollectionLocation *destination ) { Q_ASSERT(destination); Meta::TrackList list; list.append( track ); prepareCopy( list, destination ); } void CollectionLocation::prepareCopy( const Meta::TrackList &tracks, CollectionLocation *destination ) { if( !destination->isWritable() ) { CollectionLocationDelegate *delegate = Amarok::Components::collectionLocationDelegate(); delegate->notWriteable( this ); destination->deleteLater(); deleteLater(); return; } m_destination = destination; m_destination->setSource( this ); startWorkflow( tracks, false ); } void CollectionLocation::prepareCopy( Collections::QueryMaker *qm, CollectionLocation *destination ) { DEBUG_BLOCK if( !destination->isWritable() ) { CollectionLocationDelegate *delegate = Amarok::Components::collectionLocationDelegate(); delegate->notWriteable( this ); destination->deleteLater(); qm->deleteLater(); deleteLater(); return; } m_destination = destination; m_removeSources = false; m_isRemoveAction = false; connect( qm, SIGNAL(newResultReady(Meta::TrackList)), SLOT(resultReady(Meta::TrackList)) ); connect( qm, SIGNAL(queryDone()), SLOT(queryDone()) ); qm->setQueryType( Collections::QueryMaker::Track ); qm->run(); } void CollectionLocation::prepareMove( Meta::TrackPtr track, CollectionLocation *destination ) { Meta::TrackList list; list.append( track ); prepareMove( list, destination ); } void CollectionLocation::prepareMove( const Meta::TrackList &tracks, CollectionLocation *destination ) { DEBUG_BLOCK if( !destination->isWritable() ) { CollectionLocationDelegate *delegate = Amarok::Components::collectionLocationDelegate(); delegate->notWriteable( this ); destination->deleteLater(); deleteLater(); return; } m_destination = destination; m_destination->setSource( this ); startWorkflow( tracks, true ); } void CollectionLocation::prepareMove( Collections::QueryMaker *qm, CollectionLocation *destination ) { DEBUG_BLOCK if( !destination->isWritable() ) { Collections::CollectionLocationDelegate *delegate = Amarok::Components::collectionLocationDelegate(); delegate->notWriteable( this ); destination->deleteLater(); qm->deleteLater(); deleteLater(); return; } m_destination = destination; m_isRemoveAction = false; m_removeSources = true; connect( qm, SIGNAL(newResultReady(Meta::TrackList)), SLOT(resultReady(Meta::TrackList)) ); connect( qm, SIGNAL(queryDone()), SLOT(queryDone()) ); qm->setQueryType( Collections::QueryMaker::Track ); qm->run(); } void CollectionLocation::prepareRemove( const Meta::TrackList &tracks ) { DEBUG_BLOCK if( !isWritable() ) { Collections::CollectionLocationDelegate *delegate = Amarok::Components::collectionLocationDelegate(); delegate->notWriteable( this ); deleteLater(); return; } startRemoveWorkflow( tracks ); } void CollectionLocation::prepareRemove( Collections::QueryMaker *qm ) { DEBUG_BLOCK if( !isWritable() ) { Collections::CollectionLocationDelegate *delegate = Amarok::Components::collectionLocationDelegate(); delegate->notWriteable( this ); qm->deleteLater(); deleteLater(); return; } m_isRemoveAction = true; m_removeSources = false; connect( qm, SIGNAL(newResultReady(Meta::TrackList)), SLOT(resultReady(Meta::TrackList)) ); connect( qm, SIGNAL(queryDone()), SLOT(queryDone()) ); qm->setQueryType( Collections::QueryMaker::Track ); qm->run(); } bool CollectionLocation::insert( const Meta::TrackPtr &track, const QString &url ) { Q_UNUSED( track ) Q_UNUSED( url ) warning() << __PRETTY_FUNCTION__ << "Don't call this method. It exists only because" << "database importers need it. Call prepareCopy() instead."; return false; } void CollectionLocation::abort() { emit aborted(); } void CollectionLocation::getKIOCopyableUrls( const Meta::TrackList &tracks ) { DEBUG_BLOCK - QMap urls; + QMap urls; foreach( Meta::TrackPtr track, tracks ) { if( track->isPlayable() ) { urls.insert( track, track->playableUrl() ); debug() << "adding url " << track->playableUrl(); } } slotGetKIOCopyableUrlsDone( urls ); } void -CollectionLocation::copyUrlsToCollection( const QMap &sources, +CollectionLocation::copyUrlsToCollection( const QMap &sources, const Transcoding::Configuration &configuration ) { DEBUG_BLOCK //reimplement in implementations which are writable Q_UNUSED( sources ) Q_UNUSED( configuration ) slotCopyOperationFinished(); } void CollectionLocation::removeUrlsFromCollection( const Meta::TrackList &sources ) { DEBUG_BLOCK //reimplement in implementations which are writable Q_UNUSED( sources ) slotRemoveOperationFinished(); } void CollectionLocation::showSourceDialog( const Meta::TrackList &tracks, bool removeSources ) { Q_UNUSED( tracks ) Q_UNUSED( removeSources ) m_transcodingConfiguration = getDestinationTranscodingConfig(); if( m_transcodingConfiguration.isValid() ) slotShowSourceDialogDone(); else abort(); } Transcoding::Configuration CollectionLocation::getDestinationTranscodingConfig() { Transcoding::Configuration configuration( Transcoding::JUST_COPY ); Collection *destCollection = destination() ? destination()->collection() : 0; if( !destCollection ) return configuration; if( !destCollection->has() ) return configuration; QScopedPointer tc( destCollection->create() ); if( !tc ) return configuration; Transcoding::Controller* tcC = Amarok::Components::transcodingController(); QSet availableEncoders; if( tcC ) availableEncoders = tcC->availableEncoders(); Transcoding::Configuration saved = tc->savedConfiguration(); if( saved.isValid() && ( saved.isJustCopy() || availableEncoders.contains( saved.encoder() ) ) ) return saved; // saved configuration was not available or was invalid, ask user CollectionLocationDelegate *delegate = Amarok::Components::collectionLocationDelegate(); bool saveConfiguration = false; CollectionLocationDelegate::OperationType operation = CollectionLocationDelegate::Copy; if( isGoingToRemoveSources() ) operation = CollectionLocationDelegate::Move; if( collection() && collection() == destination()->collection() ) operation = CollectionLocationDelegate::Move; // organizing configuration = delegate->transcode( tc->playableFileTypes(), &saveConfiguration, operation, destCollection->prettyName(), saved ); if( configuration.isValid() ) { if( saveConfiguration ) tc->setSavedConfiguration( configuration ); else //save the trackSelection value for restore anyway tc->setSavedConfiguration( Transcoding::Configuration( Transcoding::INVALID, configuration.trackSelection() ) ); } return configuration; // may be invalid, it means user has hit cancel } void CollectionLocation::showDestinationDialog( const Meta::TrackList &tracks, bool removeSources, const Transcoding::Configuration &configuration ) { Q_UNUSED( tracks ) Q_UNUSED( configuration ) setGoingToRemoveSources( removeSources ); slotShowDestinationDialogDone(); } void CollectionLocation::showRemoveDialog( const Meta::TrackList &tracks ) { DEBUG_BLOCK if( !isHidingRemoveConfirm() ) { Collections::CollectionLocationDelegate *delegate = Amarok::Components::collectionLocationDelegate(); const bool del = delegate->reallyDelete( this, tracks ); if( !del ) slotFinishRemove(); else slotShowRemoveDialogDone(); } else slotShowRemoveDialogDone(); } QString CollectionLocation::operationText( const Transcoding::Configuration &configuration ) { if( source()->collection() == collection() ) { if( configuration.isJustCopy() ) return i18n( "Organize tracks" ); else return i18n( "Transcode and organize tracks" ); } if( isGoingToRemoveSources() ) { if( configuration.isJustCopy() ) return i18n( "Move tracks" ); else return i18n( "Transcode and move tracks" ); } else { if( configuration.isJustCopy() ) return i18n( "Copy tracks" ); else return i18n( "Transcode and copy tracks" ); } } QString CollectionLocation::operationInProgressText( const Transcoding::Configuration &configuration, int trackCount, QString destinationName ) { if( destinationName.isEmpty() ) destinationName = prettyLocation(); if( source()->collection() == collection() ) { if( configuration.isJustCopy() ) return i18np( "Organizing one track", "Organizing %1 tracks", trackCount ); else return i18np( "Transcoding and organizing one track", "Transcoding and organizing %1 tracks", trackCount ); } if( isGoingToRemoveSources() ) { if( configuration.isJustCopy() ) return i18np( "Moving one track to %2", "Moving %1 tracks to %2", trackCount, destinationName ); else return i18np( "Transcoding and moving one track to %2", "Transcoding and moving %1 tracks to %2", trackCount, destinationName ); } else { if( configuration.isJustCopy() ) return i18np( "Copying one track to %2", "Copying %1 tracks to %2", trackCount, destinationName ); else return i18np( "Transcoding and copying one track to %2", "Transcoding and copying %1 tracks to %2", trackCount, destinationName ); } } void -CollectionLocation::slotGetKIOCopyableUrlsDone( const QMap &sources ) +CollectionLocation::slotGetKIOCopyableUrlsDone( const QMap &sources ) { emit startCopy( sources, m_transcodingConfiguration ); } void CollectionLocation::slotCopyOperationFinished() { emit finishCopy(); } void CollectionLocation::slotRemoveOperationFinished() { emit finishRemove(); } void CollectionLocation::slotShowSourceDialogDone() { emit prepareOperation( m_sourceTracks, m_removeSources, m_transcodingConfiguration ); } void CollectionLocation::slotShowDestinationDialogDone() { emit operationPrepared(); } void CollectionLocation::slotShowRemoveDialogDone() { emit startRemove(); } void CollectionLocation::slotShowSourceDialog() { showSourceDialog( m_sourceTracks, m_removeSources ); } void CollectionLocation::slotPrepareOperation( const Meta::TrackList &tracks, bool removeSources, const Transcoding::Configuration &configuration ) { m_removeSources = removeSources; showDestinationDialog( tracks, removeSources, configuration ); } void CollectionLocation::slotOperationPrepared() { getKIOCopyableUrls( m_sourceTracks ); } void -CollectionLocation::slotStartCopy( const QMap &sources, +CollectionLocation::slotStartCopy( const QMap &sources, const Transcoding::Configuration &configuration ) { DEBUG_BLOCK copyUrlsToCollection( sources, configuration ); } void CollectionLocation::slotFinishCopy() { DEBUG_BLOCK if( m_removeSources ) { removeSourceTracks( m_tracksSuccessfullyTransferred ); m_sourceTracks.clear(); m_tracksSuccessfullyTransferred.clear(); } else { m_sourceTracks.clear(); m_tracksSuccessfullyTransferred.clear(); if( m_destination ) m_destination->deleteLater(); m_destination = 0; this->deleteLater(); } } void CollectionLocation::slotStartRemove() { DEBUG_BLOCK removeUrlsFromCollection( m_sourceTracks ); } void CollectionLocation::slotFinishRemove() { DEBUG_BLOCK Collections::CollectionLocationDelegate *delegate = Amarok::Components::collectionLocationDelegate(); if( m_tracksWithError.size() > 0 ) { delegate->errorDeleting( this, m_tracksWithError.keys() ); m_tracksWithError.clear(); } QStringList dirsToRemove; debug() << "remove finished updating"; foreach( Meta::TrackPtr track, m_tracksSuccessfullyTransferred ) { if(!track) continue; if( track->playableUrl().isLocalFile() ) - dirsToRemove.append( track->playableUrl().directory( KUrl::AppendTrailingSlash ) ); + dirsToRemove.append( track->playableUrl().directory( QUrl::AppendTrailingSlash ) ); } if( !dirsToRemove.isEmpty() && delegate->deleteEmptyDirs( this ) ) { debug() << "Removing empty directories"; dirsToRemove.removeDuplicates(); dirsToRemove.sort(); while( !dirsToRemove.isEmpty() ) { QDir dir( dirsToRemove.takeLast() ); if( !dir.exists() ) continue; dir.setFilter( QDir::NoDotAndDotDot ); while( !dir.isRoot() && dir.count() == 0 ) { const QString name = dir.dirName(); dir.cdUp(); if( !dir.rmdir( name ) ) { debug() << "Unable to remove " << name; break; } } } } m_tracksSuccessfullyTransferred.clear(); m_sourceTracks.clear(); this->deleteLater(); } void CollectionLocation::slotAborted() { m_destination->deleteLater(); deleteLater(); } void CollectionLocation::resultReady( const Meta::TrackList &tracks ) { m_sourceTracks << tracks; } void CollectionLocation::queryDone() { DEBUG_BLOCK QObject *obj = sender(); if( obj ) { obj->deleteLater(); } if( m_isRemoveAction ) { debug() << "we were about to remove something, lets proceed"; prepareRemove( m_sourceTracks ); } else if( m_removeSources ) { debug() << "we were about to move something, lets proceed"; prepareMove( m_sourceTracks, m_destination ); } else { debug() << "we were about to copy something, lets proceed"; prepareCopy( m_sourceTracks, m_destination ); } } void CollectionLocation::setupConnections() { connect( this, SIGNAL(prepareOperation(Meta::TrackList,bool,Transcoding::Configuration)), m_destination, SLOT(slotPrepareOperation(Meta::TrackList,bool,Transcoding::Configuration)) ); connect( m_destination, SIGNAL(operationPrepared()), SLOT(slotOperationPrepared()) ); - connect( this, SIGNAL(startCopy(QMap,Transcoding::Configuration)), - m_destination, SLOT(slotStartCopy(QMap,Transcoding::Configuration)) ); + connect( this, SIGNAL(startCopy(QMap,Transcoding::Configuration)), + m_destination, SLOT(slotStartCopy(QMap,Transcoding::Configuration)) ); connect( m_destination, SIGNAL(finishCopy()), this, SLOT(slotFinishCopy()) ); connect( this, SIGNAL(aborted()), SLOT(slotAborted()) ); connect( m_destination, SIGNAL(aborted()), SLOT(slotAborted()) ); } void CollectionLocation::setupRemoveConnections() { connect( this, SIGNAL(aborted()), SLOT(slotAborted()) ); connect( this, SIGNAL(startRemove()), this, SLOT(slotStartRemove()) ); connect( this, SIGNAL(finishRemove()), this, SLOT(slotFinishRemove()) ); } void CollectionLocation::startWorkflow( const Meta::TrackList &tracks, bool removeSources ) { DEBUG_BLOCK m_removeSources = removeSources; m_sourceTracks = tracks; setupConnections(); if( tracks.size() <= 0 ) abort(); else // show dialog in next mainloop iteration so that prepare[Something]() returns quickly QTimer::singleShot( 0, this, SLOT(slotShowSourceDialog()) ); } void CollectionLocation::startRemoveWorkflow( const Meta::TrackList &tracks ) { DEBUG_BLOCK m_sourceTracks = tracks; setupRemoveConnections(); if( tracks.isEmpty() ) abort(); else showRemoveDialog( tracks ); } void CollectionLocation::removeSourceTracks( const Meta::TrackList &tracks ) { DEBUG_BLOCK debug() << "Transfer errors:" << m_tracksWithError.count() << "of" << tracks.count(); foreach( Meta::TrackPtr track, m_tracksWithError.keys() ) { debug() << "transfer error for track" << track->playableUrl(); } QSet toRemove = QSet::fromList( tracks ); QSet errored = QSet::fromList( m_tracksWithError.keys() ); toRemove.subtract( errored ); // start the remove workflow setHidingRemoveConfirm( true ); prepareRemove( toRemove.toList() ); } CollectionLocation* CollectionLocation::source() const { return m_source; } CollectionLocation* CollectionLocation::destination() const { return m_destination; } void CollectionLocation::setSource( CollectionLocation *source ) { m_source = source; } void CollectionLocation::transferSuccessful( const Meta::TrackPtr &track ) { m_tracksSuccessfullyTransferred.append( track ); } bool CollectionLocation::isGoingToRemoveSources() const { return m_removeSources; } void CollectionLocation::setGoingToRemoveSources( bool removeSources ) { m_removeSources = removeSources; } bool CollectionLocation::isHidingRemoveConfirm() const { return m_noRemoveConfirmation; } void CollectionLocation::setHidingRemoveConfirm( bool hideRemoveConfirm ) { m_noRemoveConfirmation = hideRemoveConfirm; } void CollectionLocation::transferError( const Meta::TrackPtr &track, const QString &error ) { m_tracksWithError.insert( track, error ); } diff --git a/src/core/collections/CollectionLocation.h b/src/core/collections/CollectionLocation.h index 7a1ed1e924..c8220bb95c 100644 --- a/src/core/collections/CollectionLocation.h +++ b/src/core/collections/CollectionLocation.h @@ -1,425 +1,425 @@ /**************************************************************************************** * Copyright (c) 2007-2008 Maximilian Kossick * * Copyright (c) 2008 Jason A. Donenfeld * * Copyright (c) 2010 Casey Link * * * * 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, see . * ****************************************************************************************/ #ifndef AMAROK_COLLECTIONLOCATION_H #define AMAROK_COLLECTIONLOCATION_H #include "core/amarokcore_export.h" #include "core/meta/forward_declarations.h" #include "core/transcoding/TranscodingConfiguration.h" #include #include -#include +#include namespace Collections { class Collection; class QueryMaker; /** This base class defines the methods necessary to allow the copying and moving of tracks between different collections in a generic way. This class should be used as follows in client code: - select a source and a destination CollectionLocation - call prepareCopy or prepareMove on the source CollectionLocation - forget about the rest of the workflow Implementations which are writable must reimplement the following methods - prettyLocation() - isWritable() - remove( Meta::Track ) - - copyUrlsToCollection( QMap ) + - copyUrlsToCollection( QMap ) Writable collections that are also organizable should reimplement isOrganizable(). Organizable means that the user is able to decide (to varying degrees, the details depend on the actual collection) where the files are stored in the filesystem (or some kind of VFS). An example would be the local collection, where the user can select the directory structure that Amarok uses to store the files. An example for a writable collection that is not organizable are ipods, where the user has no control about the actual location of the music files (they are automatically stored in a not human-readable form). Implementations which are only readable can reimplement getKIOCopyableUrls( Meta::TrackList ) if it is necessary, but can use the default implementations if possible. Implementations that have a string expressable location(s), such as a URL or path on disk should reimplement actualLocation(). Implementations that need additional information provided by the user have to implement showSourceDialog() and showDestinationDialog(), depending on whether the information is required in the source, the destination, or both. The methods will be called in the following order: startWorkflow (source) showSourceDialog (source) (reimplementable) slotShowSourceDialogDone (source) slotPrepareOperation (destination) showDestinationDialog (destination) (reimplementable) slotShowDestinationDialogDone (destination) slotOperationPrepared (source) getKIOCopyableUrls (source) (reimplementable) slotGetKIOCopyableUrlsDone (source) slotStartCopy (destination) copyUrlsToCollection (destination) (reimplementable) slotCopyOperationFinished (destination) slotFinishCopy (source) To provide removal ability, it is required to reimplement removeUrlsFromCollection, and this function must call slotRemoveOperationFinished() when it is done. Optionally, showRemoveDialog can be reimplemented to customize the warning displayed before a removal, and this function must call slotShowRemoveDialogDone when finished. The methods for remove will be called in the following order: startRemoveWorkflow showRemoveDialog (reimplementable) slotShowRemoveDialogDone slotStartRemove removeUrlsFromCollection (reimplementable) slotRemoveOperationFinished slotFinishRemove */ class AMAROK_CORE_EXPORT CollectionLocation : public QObject { Q_OBJECT //testing only do not use these properties in anything but tests Q_PROPERTY( bool removeSources READ getRemoveSources WRITE setRemoveSources DESIGNABLE false SCRIPTABLE false ) public: CollectionLocation(); CollectionLocation( Collections::Collection *parentCollection ); virtual ~CollectionLocation(); /** Returns a pointer to the collection location's corresponding collection. @return a pointer to the collection location's corresponding collection */ virtual Collections::Collection *collection() const; /** a displayable string representation of the collection location. use the return value of this method to display the collection location to the user. @return a string representation of the collection location */ virtual QString prettyLocation() const; /** Returns a list of machine usable strings representingthe collection location. For example, a local collection would return a list of paths where tracks are stored, while an Ampache collection would return a list with one string containing the URL of an ampache server. An iPod collection and a MTP device collection are examples of collections that do not need to reimplement this method. */ virtual QStringList actualLocation() const; /** Returns whether the collection location is writable or not. For example, a local collection or an ipod collection would return true, a daap collection or a service collection false. The value returned by this method indicates if it is possible to copy tracks to the collection, and if it is generally possible to remove/delete files from the collection. @return @c true if the collection location is writable @return @c false if the collection location is not writable */ virtual bool isWritable() const; /** Returns whether the collection is organizable or not. Organizable collections allow move operations where the source and destination collection are the same. @return @c true if the collection location is organizable, false otherwise */ virtual bool isOrganizable() const; /** Convenience method for copying a single track. @see prepareCopy( Meta::TrackList, CollectionLocation* ) */ void prepareCopy( Meta::TrackPtr track, CollectionLocation *destination ); /** Schedule copying of @param tracks to collection location @param destination. This method takes ownership of the @param destination, you may not reference or delete it after this call. This method returns immediately and the actual copy is performed in the event loop and/or another thread. */ void prepareCopy( const Meta::TrackList &tracks, CollectionLocation *destination ); /** Convenience method for copying tracks based on QueryMaker restults, takes ownership of the @param qm. @see prepareCopy( Meta::TrackList, CollectionLocation* ) */ void prepareCopy( Collections::QueryMaker *qm, CollectionLocation *destination ); /** * Convenience method for moving a single track. * @see prepareMove( Meta::TrackList, CollectionLocation* ) */ void prepareMove( Meta::TrackPtr track, CollectionLocation *destination ); /** Schedule moving of @param tracks to collection location @param destination. This method takes ownership of the @param destination, you may not reference or delete it after this call. This method returns immediately and the actual move is performed in the event loop and/or another thread. */ void prepareMove( const Meta::TrackList &tracks, CollectionLocation *destination ); /** Convenience method for moving tracks based on QueryMaker restults, takes ownership of the @param qm. @see prepareMove( Meta::TrackList, CollectionLocation* ) */ void prepareMove( Collections::QueryMaker *qm, CollectionLocation *destination ); /** Starts workflow for removing tracks. */ void prepareRemove( const Meta::TrackList &tracks ); /** Convenience method for removing tracks selected by QueryMaker, takes ownership of the @param qm. @see prepareRemove( Meta::TrackList ) */ void prepareRemove( Collections::QueryMaker *qm ); /** * Adds or merges a track to the collection (not to the disk) * Inserts a set of TrackPtrs directly into the database without needing to actual move any files * This is a hack required by the DatabaseImporter * TODO: Remove this hack * @return true if the database entry was inserted, false otherwise */ virtual bool insert( const Meta::TrackPtr &track, const QString &url ); /** explicitly inform the source collection of successful transfer. The source collection will only remove files (if necessary) for which this method was called. */ void transferSuccessful( const Meta::TrackPtr &track ); /** * tells the source location that an error occurred during the transfer of the file */ virtual void transferError( const Meta::TrackPtr &track, const QString &error ); signals: - void startCopy( const QMap &sources, + void startCopy( const QMap &sources, const Transcoding::Configuration & ); void finishCopy(); void startRemove(); void finishRemove(); void prepareOperation( const Meta::TrackList &tracks, bool removeSources, const Transcoding::Configuration & ); void operationPrepared(); void aborted(); protected: /** * aborts the workflow */ void abort(); /** * allows the destination location to access the source CollectionLocation. * note: subclasses do not take ownership of the pointer */ CollectionLocation* source() const; /** * allows the source location to access the destination CollectionLocation. * Pointer may be null! * note: subclasses do not take ownership of the pointer */ CollectionLocation* destination() const; /** this method is called on the source location, and should return a list of urls which the destination location can copy using KIO. You must call - slotGetKIOCopyableUrlsDone( QMap ) after retrieving the + slotGetKIOCopyableUrlsDone( QMap ) after retrieving the urls. The order of urls passed to that method has to be the same as the order of the tracks passed to this method. */ virtual void getKIOCopyableUrls( const Meta::TrackList &tracks ); /** this method is called on the destination. reimplement it if your implementation is writable. You must call slotCopyOperationFinished() when you are done copying the files. Before calling slotCopyOperationFinished(), you should call source()->transferSuccessful() for every source track that was for sure successfully copied to destination collection. Only such marked tracks are then removed in case of a "move" action. */ - virtual void copyUrlsToCollection( const QMap &sources, + virtual void copyUrlsToCollection( const QMap &sources, const Transcoding::Configuration &configuration ); /** this method is called on the collection you want to remove tracks from. it must be reimplemented if your collection is writable and you wish to implement removing tracks. You must call slotRemoveOperationFinished() when you are done removing the files. Before calling slotRemoveOperationFinished(), you should call transferSuccessful() for every track that was successfully deleted. CollectionLocation then scans directories of such tracks and allows user to remove empty ones. */ virtual void removeUrlsFromCollection( const Meta::TrackList &sources ); /** * this method is called on the source. It allows the source CollectionLocation to * show a dialog. Classes that reimplement this method must call * slotShowSourceDialogDone() after they have acquired all necessary information from the user. * * Default implementation calls getDestinationTranscodingConfig() which may ask * user. If you reimplement this you may (or not) call this base method instead * of calling slotShowDestinationDialogDone() to support transcoding. */ virtual void showSourceDialog( const Meta::TrackList &tracks, bool removeSources ); /** * Get transcoding configuration to use when transferring tracks to destination. * If destination collection doesn't support transcoding, JUST_COPY configuration * is returned, otherwise preferred configuration is read or user is asked. * Returns invalid configuration in case user has hit cancel or closed the dialog. * * This method is meant to be called by source collection. */ virtual Transcoding::Configuration getDestinationTranscodingConfig(); /** * This method is called on the destination. It allows the destination * CollectionLocation to show a dialog. Classes that reimplement this method * must call slotShowDestinationDialogDone() after they have acquired all necessary * information from the user. * * Default implementation calls setGoingToRemoveSources( removeSources ) so that * isGoingToRemoveSources() is available on destination, too and then calls * slotShowDestinationDialogDone() */ virtual void showDestinationDialog( const Meta::TrackList &tracks, bool removeSources, const Transcoding::Configuration &configuration ); /** * this methods allows the collection to show a warning dialog before tracks are removed, * rather than using the default provided. Classes that reimplement this method must call * slotShowRemoveDialogDone() after they are finished. */ virtual void showRemoveDialog( const Meta::TrackList &tracks ); /** * Get nice localised string describing the current operation based on transcoding * configuraiton and isGoingToRemoveSources(); meant to be called by destination * collection. * * @return "Copy Tracks", "Transcode and Organize Tracks" etc. */ QString operationText( const Transcoding::Configuration &configuration ); /** * Get nice localised string that can be used as progress bar text for the current * operation; meant to be called by the destination collection. * * @param trackCount number of tracks in the transfer * @param destinationName pretty localised name of the destination collection; * prettyLocation() is used if the string is empty or not specified * * @return "Transcoding and moving tracks to " etc. */ QString operationInProgressText( const Transcoding::Configuration &configuration, int trackCount, QString destinationName = QString() ); /** * Sets or gets whether some source files may be removed */ virtual bool isGoingToRemoveSources() const; virtual void setGoingToRemoveSources( bool removeSources ); /** * Sets or gets whether to stifle the removal confirmation */ virtual bool isHidingRemoveConfirm() const; virtual void setHidingRemoveConfirm( bool hideRemoveConfirm ); protected slots: /** * this slot has to be called from getKIOCopyableUrls( Meta::TrackList ) * Please note: the order of urls in the argument has to be the same as in the * tracklist */ - void slotGetKIOCopyableUrlsDone( const QMap &sources ); + void slotGetKIOCopyableUrlsDone( const QMap &sources ); void slotCopyOperationFinished(); void slotRemoveOperationFinished(); void slotShowSourceDialogDone(); void slotShowRemoveDialogDone(); void slotShowDestinationDialogDone(); private slots: void slotShowSourceDialog(); // trick to show dialog in next mainloop iteration void slotPrepareOperation( const Meta::TrackList &tracks, bool removeSources, const Transcoding::Configuration &configuration ); void slotOperationPrepared(); - void slotStartCopy( const QMap &sources, + void slotStartCopy( const QMap &sources, const Transcoding::Configuration &configuration ); void slotFinishCopy(); void slotStartRemove(); void slotFinishRemove(); void slotAborted(); void resultReady( const Meta::TrackList &tracks ); void queryDone(); private: void setupConnections(); void setupRemoveConnections(); void startWorkflow( const Meta::TrackList &tracks, bool removeSources ); void startRemoveWorkflow( const Meta::TrackList &tracks ); void startRemove( const Meta::TrackList &tracks ); void removeSourceTracks( const Meta::TrackList &tracks ); void setSource( CollectionLocation *source ); //only used in the source CollectionLocation CollectionLocation *m_destination; //only used in destination CollectionLocation CollectionLocation *m_source; Meta::TrackList getSourceTracks() const { return m_sourceTracks; } void setSourceTracks( Meta::TrackList tracks ) { m_sourceTracks = tracks; } Meta::TrackList m_sourceTracks; Collections::Collection *m_parentCollection; bool getRemoveSources() const { return m_removeSources; } void setRemoveSources( bool removeSources ) { m_removeSources = removeSources; } bool m_removeSources; bool m_isRemoveAction; bool m_noRemoveConfirmation; Transcoding::Configuration m_transcodingConfiguration; //used by the source collection to store the tracks that were successfully //copied by the destination and can be removed as part of a move Meta::TrackList m_tracksSuccessfullyTransferred; QMap m_tracksWithError; }; } //namespace Collections #endif diff --git a/src/core/collections/support/TrackForUrlWorker.cpp b/src/core/collections/support/TrackForUrlWorker.cpp index 4855d30826..fc1b812247 100644 --- a/src/core/collections/support/TrackForUrlWorker.cpp +++ b/src/core/collections/support/TrackForUrlWorker.cpp @@ -1,43 +1,43 @@ /**************************************************************************************** * Copyright (c) 2009 Casey Link * * * * 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, see . * ****************************************************************************************/ #include "TrackForUrlWorker.h" #include "core/meta/Meta.h" -Amarok::TrackForUrlWorker::TrackForUrlWorker( const KUrl &url ) +Amarok::TrackForUrlWorker::TrackForUrlWorker( const QUrl &url ) : ThreadWeaver::Job() , m_url( url ) { connect( this, SIGNAL(done(ThreadWeaver::Job*)), SLOT(completeJob()) ); } Amarok::TrackForUrlWorker::TrackForUrlWorker( const QString &url ) : ThreadWeaver::Job() - , m_url( KUrl( url ) ) + , m_url( QUrl( url ) ) { connect( this, SIGNAL(done(ThreadWeaver::Job*)), SLOT(completeJob()) ); } Amarok::TrackForUrlWorker::~TrackForUrlWorker() {} void Amarok::TrackForUrlWorker::completeJob() { emit finishedLookup( m_track ); deleteLater(); } diff --git a/src/core/collections/support/TrackForUrlWorker.h b/src/core/collections/support/TrackForUrlWorker.h index 7696a1499f..67ea562d38 100644 --- a/src/core/collections/support/TrackForUrlWorker.h +++ b/src/core/collections/support/TrackForUrlWorker.h @@ -1,57 +1,57 @@ /**************************************************************************************** * Copyright (c) 2009 Casey Link * * * * 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, see . * ****************************************************************************************/ #ifndef TRACKFORURLWORKER_H #define TRACKFORURLWORKER_H #include "core/amarokcore_export.h" #include "core/support/Amarok.h" #include "core/meta/forward_declarations.h" -#include +#include #include namespace Amarok { /** * Derive from this class and implement the run() method to set mTrack. * @author Casey Link */ class AMAROK_CORE_EXPORT TrackForUrlWorker : public ThreadWeaver::Job { Q_OBJECT public: - TrackForUrlWorker( const KUrl &url ); + TrackForUrlWorker( const QUrl &url ); TrackForUrlWorker( const QString &url ); ~TrackForUrlWorker(); virtual void run() = 0; signals: void finishedLookup( const Meta::TrackPtr &track ); protected: - KUrl m_url; + QUrl m_url; Meta::TrackPtr m_track; private slots: void completeJob(); }; } #endif // TRACKFORURLWORKER_H diff --git a/src/core/meta/Meta.h b/src/core/meta/Meta.h index e3b90f52aa..acb00c69f2 100644 --- a/src/core/meta/Meta.h +++ b/src/core/meta/Meta.h @@ -1,388 +1,388 @@ /**************************************************************************************** * Copyright (c) 2007 Maximilian Kossick * * Copyright (c) 2008 Mark Kretschmann * * * * 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, see . * ****************************************************************************************/ #ifndef AMAROK_META_H #define AMAROK_META_H #include "MetaReplayGain.h" #include "core/meta/Base.h" #include #include #include #include #include -#include +#include namespace Collections { class Collection; class QueryMaker; } class PersistentStatisticsStore; namespace Meta { class AMAROK_CORE_EXPORT Track : public Base { public: /** used to display the trackname, should never be empty, returns prettyUrl() by default if name() is empty */ virtual QString prettyName() const; /** an url which can be played by the engine backends */ - virtual KUrl playableUrl() const = 0; + virtual QUrl playableUrl() const = 0; /** an url for display purposes */ virtual QString prettyUrl() const = 0; /** * A fake url which is unique for this track. Use this if you need a key for * the track. */ virtual QString uidUrl() const = 0; /** * Return whether playableUrl() will return a valid & readable playable url. * * Convenience method that just checks whether notPlayableReason() is empty. */ bool isPlayable() const; /** * Return user-readable localized reason why isPlayable() is false. * * Subclasses must return a non-empty (localized) string if this track is not * playable (i.e. playableUrl() won't return a valid url) and an empty string * otherwise. * * This method is used to implement convenience Meta::Track::isPlayable() * method. */ virtual QString notPlayableReason() const = 0; /** Returns the album this track belongs to */ virtual AlbumPtr album() const = 0; /** Returns the artist of this track */ virtual ArtistPtr artist() const = 0; /** Returns the composer of this track */ virtual ComposerPtr composer() const = 0; /** Returns the genre of this track */ virtual GenrePtr genre() const = 0; /** Returns the year of this track */ virtual YearPtr year() const = 0; /** * Returns the labels that are assigned to a track. */ virtual Meta::LabelList labels() const; /** Returns the BPM of this track */ virtual qreal bpm() const = 0; /** Returns the comment of this track */ virtual QString comment() const = 0; /** Returns the length of this track in milliseconds, or 0 if unknown */ virtual qint64 length() const = 0; /** Returns the filesize of this track in bytes */ virtual int filesize() const = 0; /** Returns the sample rate of this track */ virtual int sampleRate() const = 0; /** Returns the bitrate o this track in kbps (kilo BITS per second) */ virtual int bitrate() const = 0; /** Returns the time when the track was added to the collection, or an invalid QDateTime if the time is not known. */ virtual QDateTime createDate() const; /** Returns the time when the track was last modified (before being added to the collection) or an invalid QDateTime if the time is not known. */ virtual QDateTime modifyDate() const; /** Returns the track number of this track */ virtual int trackNumber() const = 0; /** Returns the discnumber of this track */ virtual int discNumber() const = 0; /** * Returns the gain adjustment for a given replay gain mode. * * Should return @c 0 if no replay gain value is known. * * Should return the track replay gain if the album gain is requested but * is not available. */ virtual qreal replayGain( ReplayGainTag mode ) const; /** * Returns the type of this track, e.g. "ogg", "mp3", "stream" * * TODO: change return type to Amarok::FileType enum. Clients needing * user-representation would call FileTypeSupport::toLocalizedString() */ virtual QString type() const = 0; /** tell the track to perform any prerequisite * operations before playing */ virtual void prepareToPlay(); /** tell the track object that amarok finished playing it. The argument is the percentage of the track which was played, in the range 0 to 1*/ virtual void finishedPlaying( double playedFraction ); /** returns true if the track is part of a collection false otherwise */ virtual bool inCollection() const; /** returns the collection that the track is part of, or 0 iff inCollection() returns false */ virtual Collections::Collection* collection() const; /** get the cached lyrics for the track. returns an empty string if no cached lyrics are available */ virtual QString cachedLyrics() const; /** set the cached lyrics for the track */ virtual void setCachedLyrics( const QString &lyrics ); /** Adds a label to the track. Does nothing if the track already has the given label. */ virtual void addLabel( const QString &label ); /** Adds a label to the track. Does nothing if the track already has the given label. */ virtual void addLabel( const Meta::LabelPtr &label ); /** Removes a lbel from a track. Does nothing if the track does not actually have the label assigned to it. */ virtual void removeLabel( const Meta::LabelPtr &label ); virtual bool operator==( const Track &track ) const; static bool lessThan( const TrackPtr& left, const TrackPtr& right ); /** * Return a pointer to TrackEditor interface that allows you to edit metadata * of this track. May be null, which signifies that the track is not editable. * * This is a replacement to ::create() with more * well-defined memory management and nicer implementation possibilities. * (multiple inheritance and returning self) * * Default implementation returns null pointer. */ virtual TrackEditorPtr editor(); /** * Return a pointer to track's Statistics interface. May never be null. * * Subclasses: always return the default implementation instead of returning 0. */ virtual StatisticsPtr statistics(); ConstStatisticsPtr statistics() const; // allow const statistics methods on const tracks protected: friend class ::PersistentStatisticsStore; // so that it can call notifyObservers virtual void notifyObservers() const; /** * Helper method for subclasses to implement notPlayableReason(). * Checks network status and returns a non-empty reason string if * it isn't online. */ QString networkNotPlayableReason() const; /** * Helper method for subclasses to implement notPlayableReason(). * Checks, in order, if the file exists, if it is a file and if * the file is readable */ QString localFileNotPlayableReason( const QString &path ) const; }; class AMAROK_CORE_EXPORT Artist : public Base { public: virtual QString prettyName() const; /** returns all tracks by this artist */ virtual TrackList tracks() = 0; virtual bool operator==( const Meta::Artist &artist ) const; virtual QString sortableName() const; protected: virtual void notifyObservers() const; private: mutable QString m_sortableName; }; /** * Represents an album. * * Most collections do not store a specific album object. Instead an album is just * a property of a track, a container containing one or more tracks. * * Collections should provide an album for every track as the collection browser * will, depending on the setting, only display tracks inside albums. * * For all albums in a compilation the pair album-title/album-artist should be * unique as this pair is used as a key in several places. * * Albums without an artist are called compilations. Albums without a title but * with an artist should contain all singles of the specific artist. There should * be one album without title and artist for all the rest. */ class AMAROK_CORE_EXPORT Album : public Base { public: virtual QString prettyName() const; /** * Whether this album is considered to be a compilation of tracks from various * artists. */ virtual bool isCompilation() const = 0; /** * Whether toggling the compilation status is currenlty supported. Default * implementation returns false. */ virtual bool canUpdateCompilation() const { return false; } /** * Set compilation status. You should check canUpdateCompilation() first. */ virtual void setCompilation( bool isCompilation ) { Q_UNUSED( isCompilation ) } /** Returns true if this album has an album artist */ virtual bool hasAlbumArtist() const = 0; /** Returns a pointer to the album's artist */ virtual ArtistPtr albumArtist() const = 0; /** returns all tracks on this album */ virtual TrackList tracks() = 0; /** * A note about image sizes: * when size is <= 1, return the full size image */ /** returns true if the album has a cover set */ virtual bool hasImage( int size = 0 ) const { Q_UNUSED( size ); return false; } /** Returns the image for the album, usually the cover image. The default implementation returns an null image. If you need a pixmap call The::coverCache()->getCover( album, size ) instead. That function also returns a "nocover" pixmap */ virtual QImage image( int size = 0 ) const; /** Returns the image location on disk. The mpris interface is using this information for notifications so it better is a local file url. */ - virtual KUrl imageLocation( int size = 0 ) { Q_UNUSED( size ); return KUrl(); } + virtual QUrl imageLocation( int size = 0 ) { Q_UNUSED( size ); return QUrl(); } /** Returns true if it is possible to update the cover of the album */ virtual bool canUpdateImage() const { return false; } /** updates the cover of the album @param image The large scale image that should be used as cover for the album. Note: the parameter should not be a QPixmap as a pixmap can only be created reliable in a UI thread. */ virtual void setImage( const QImage &image ) { Q_UNUSED( image ); } /** removes the album art */ virtual void removeImage() { } /** don't automatically fetch artwork */ virtual void setSuppressImageAutoFetch( const bool suppress ) { Q_UNUSED( suppress ); } /** should automatic artwork retrieval be suppressed? */ virtual bool suppressImageAutoFetch() const { return false; } virtual bool operator==( const Meta::Album &album ) const; protected: virtual void notifyObservers() const; }; class AMAROK_CORE_EXPORT Composer : public Base { public: virtual QString prettyName() const; /** returns all tracks by this composer */ virtual TrackList tracks() = 0; virtual bool operator==( const Meta::Composer &composer ) const; protected: virtual void notifyObservers() const; }; class AMAROK_CORE_EXPORT Genre : public Base { public: virtual QString prettyName() const; /** returns all tracks which belong to the genre */ virtual TrackList tracks() = 0; virtual bool operator==( const Meta::Genre &genre ) const; protected: virtual void notifyObservers() const; }; class AMAROK_CORE_EXPORT Year : public Base { public: /** * Returns the year this object represents. * number of 0 is considered unset. */ virtual int year() const { return name().toInt(); } /** returns all tracks which are tagged with this year */ virtual TrackList tracks() = 0; virtual bool operator==( const Meta::Year &year ) const; protected: virtual void notifyObservers() const; }; /** * A Label represents an arbitrary classification of a Track. */ class AMAROK_CORE_EXPORT Label : public Base { // we need nothing more than what Meta::Base has }; } Q_DECLARE_METATYPE( Meta::TrackPtr ) Q_DECLARE_METATYPE( Meta::TrackList ) Q_DECLARE_METATYPE( Meta::ArtistPtr ) Q_DECLARE_METATYPE( Meta::ArtistList ) Q_DECLARE_METATYPE( Meta::AlbumPtr ) Q_DECLARE_METATYPE( Meta::AlbumList ) Q_DECLARE_METATYPE( Meta::ComposerPtr ) Q_DECLARE_METATYPE( Meta::ComposerList ) Q_DECLARE_METATYPE( Meta::GenrePtr ) Q_DECLARE_METATYPE( Meta::GenreList ) Q_DECLARE_METATYPE( Meta::YearPtr ) Q_DECLARE_METATYPE( Meta::YearList ) Q_DECLARE_METATYPE( Meta::LabelPtr ) Q_DECLARE_METATYPE( Meta::LabelList ) #endif /* AMAROK_META_H */ diff --git a/src/core/meta/support/MetaUtility.cpp b/src/core/meta/support/MetaUtility.cpp index 5e89684dde..8e01534de8 100644 --- a/src/core/meta/support/MetaUtility.cpp +++ b/src/core/meta/support/MetaUtility.cpp @@ -1,465 +1,465 @@ /**************************************************************************************** * Copyright (c) 2007 Maximilian Kossick * * * * 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, see . * ****************************************************************************************/ #include "core/meta/support/MetaUtility.h" #include "core/capabilities/Capability.h" #include "core/meta/Meta.h" #include "core/meta/Statistics.h" #include "core/meta/TrackEditor.h" #include "core/support/Debug.h" #include #include #include #include #include static const QString XESAM_ALBUM = "http://freedesktop.org/standards/xesam/1.0/core#album"; static const QString XESAM_ALBUMARTIST = "http://freedesktop.org/standards/xesam/1.0/core#albumArtist"; static const QString XESAM_ARTIST = "http://freedesktop.org/standards/xesam/1.0/core#artist"; static const QString XESAM_BITRATE = "http://freedesktop.org/standards/xesam/1.0/core#audioBitrate"; static const QString XESAM_BPM = "http://freedesktop.org/standards/xesam/1.0/core#audioBPM"; static const QString XESAM_CODEC = "http://freedesktop.org/standards/xesam/1.0/core#audioCodec"; static const QString XESAM_COMMENT = "http://freedesktop.org/standards/xesam/1.0/core#comment"; static const QString XESAM_COMPOSER = "http://freedesktop.org/standards/xesam/1.0/core#composer"; static const QString XESAM_DISCNUMBER = "http://freedesktop.org/standards/xesam/1.0/core#discNumber"; static const QString XESAM_FILESIZE = "http://freedesktop.org/standards/xesam/1.0/core#size"; static const QString XESAM_GENRE = "http://freedesktop.org/standards/xesam/1.0/core#genre"; static const QString XESAM_LENGTH = "http://freedesktop.org/standards/xesam/1.0/core#mediaDuration"; static const QString XESAM_RATING = "http://freedesktop.org/standards/xesam/1.0/core#userRating"; static const QString XESAM_SAMPLERATE = "http://freedesktop.org/standards/xesam/1.0/core#audioSampleRate"; static const QString XESAM_TITLE = "http://freedesktop.org/standards/xesam/1.0/core#title"; static const QString XESAM_TRACKNUMBER = "http://freedesktop.org/standards/xesam/1.0/core#trackNumber"; static const QString XESAM_URL = "http://freedesktop.org/standards/xesam/1.0/core#url"; static const QString XESAM_YEAR = "http://freedesktop.org/standards/xesam/1.0/core#contentCreated"; static const QString XESAM_SCORE = "http://freedesktop.org/standards/xesam/1.0/core#autoRating"; static const QString XESAM_PLAYCOUNT = "http://freedesktop.org/standards/xesam/1.0/core#useCount"; static const QString XESAM_FIRST_PLAYED = "http://freedesktop.org/standards/xesam/1.0/core#firstUsed"; static const QString XESAM_LAST_PLAYED = "http://freedesktop.org/standards/xesam/1.0/core#lastUsed"; static const QString XESAM_ID = "http://freedesktop.org/standards/xesam/1.0/core#id"; //static bool conversionMapsInitialised = false; QVariantMap Meta::Field::mapFromTrack( const Meta::TrackPtr track ) { QVariantMap map; if( !track ) return map; if( track->name().isEmpty() ) map.insert( Meta::Field::TITLE, QVariant( track->prettyName() ) ); else map.insert( Meta::Field::TITLE, QVariant( track->name() ) ); if( track->artist() && !track->artist()->name().isEmpty() ) map.insert( Meta::Field::ARTIST, QVariant( track->artist()->name() ) ); if( track->album() && !track->album()->name().isEmpty() ) { map.insert( Meta::Field::ALBUM, QVariant( track->album()->name() ) ); if( track->album()->hasAlbumArtist() && !track->album()->albumArtist()->name().isEmpty() ) map.insert( Meta::Field::ALBUMARTIST, QVariant( track->album()->albumArtist()->name() ) ); } if( track->filesize() ) map.insert( Meta::Field::FILESIZE, QVariant( track->filesize() ) ); if( track->genre() && !track->genre()->name().isEmpty() ) map.insert( Meta::Field::GENRE, QVariant( track->genre()->name() ) ); if( track->composer() && !track->composer()->name().isEmpty() ) map.insert( Meta::Field::COMPOSER, QVariant( track->composer()->name() ) ); if( track->year() && !track->year()->name().isEmpty() ) map.insert( Meta::Field::YEAR, QVariant( track->year()->name() ) ); if( !track->comment().isEmpty() ) map.insert( Meta::Field::COMMENT, QVariant( track->comment() ) ); if( track->trackNumber() ) map.insert( Meta::Field::TRACKNUMBER, QVariant( track->trackNumber() ) ); if( track->discNumber() ) map.insert( Meta::Field::DISCNUMBER, QVariant( track->discNumber() ) ); if( track->bitrate() ) map.insert( Meta::Field::BITRATE, QVariant( track->bitrate() ) ); if( track->length() ) map.insert( Meta::Field::LENGTH, QVariant( track->length() ) ); if( track->sampleRate() ) map.insert( Meta::Field::SAMPLERATE, QVariant( track->sampleRate() ) ); if( track->bpm() >= 0.0) map.insert( Meta::Field::BPM, QVariant( track->bpm() ) ); map.insert( Meta::Field::UNIQUEID, QVariant( track->uidUrl() ) ); map.insert( Meta::Field::URL, QVariant( track->prettyUrl() ) ); Meta::ConstStatisticsPtr statistics = track->statistics(); map.insert( Meta::Field::RATING, QVariant( statistics->rating() ) ); map.insert( Meta::Field::SCORE, QVariant( statistics->score() ) ); map.insert( Meta::Field::PLAYCOUNT, QVariant( statistics->playCount() ) ); map.insert( Meta::Field::LAST_PLAYED, QVariant( statistics->lastPlayed() ) ); map.insert( Meta::Field::FIRST_PLAYED, QVariant( statistics->firstPlayed() ) ); return map; } QVariantMap Meta::Field::mprisMapFromTrack( const Meta::TrackPtr track ) { QVariantMap map; if( track ) { // MANDATORY: map["location"] = track->playableUrl().url(); // INFORMATIONAL: map["title"] = track->prettyName(); if( track->artist() ) map["artist"] = track->artist()->name(); if( track->album() ) { map["album"] = track->album()->name(); if( track->album()->hasAlbumArtist() && !track->album()->albumArtist()->name().isEmpty() ) map[ "albumartist" ] = track->album()->albumArtist()->name(); QImage image = track->album()->image(); - KUrl url = track->album()->imageLocation().url(); + QUrl url = track->album()->imageLocation().url(); if ( url.isValid() && !url.isLocalFile() ) { // embedded id? Request a version to be put in the cache int width = track->album()->image().width(); url = track->album()->imageLocation( width ).url(); debug() << "MPRIS: New location for width" << width << "is" << url; } if ( url.isValid() && url.isLocalFile() ) map["arturl"] = QString::fromLatin1( url.toEncoded() ); } map["tracknumber"] = track->trackNumber(); map["time"] = track->length() / 1000; map["mtime"] = track->length(); if( track->genre() ) map["genre"] = track->genre()->name(); map["comment"] = track->comment(); map["rating"] = track->statistics()->rating()/2; //out of 5, not 10. if( track->year() ) map["year"] = track->year()->name(); //TODO: external service meta info // TECHNICAL: map["audio-bitrate"] = track->bitrate(); map["audio-samplerate"] = track->sampleRate(); //amarok has no video-bitrate // EXTRA Amarok specific const QString lyrics = track->cachedLyrics(); if( !lyrics.isEmpty() ) map["lyrics"] = lyrics; } return map; } QVariantMap Meta::Field::mpris20MapFromTrack( const Meta::TrackPtr track ) { QVariantMap map; if( track ) { // We do not set mpris::trackid here because it depends on the position // of the track in the playlist map["mpris:length"] = track->length() * 1000; // microseconds // get strong pointers (BR 317980) Meta::AlbumPtr album = track->album(); Meta::ArtistPtr artist = track->artist(); Meta::ComposerPtr composer = track->composer(); Meta::YearPtr year = track->year(); Meta::GenrePtr genre = track->genre(); Meta::ConstStatisticsPtr statistics = track->statistics(); if( album ) { QImage image = album->image(); - KUrl url = album->imageLocation().url(); + QUrl url = album->imageLocation().url(); debug() << "MPRIS2: Album image location is" << url; if ( url.isValid() && !url.isLocalFile() ) { // embedded id? Request a version to be put in the cache int width = album->image().width(); url = album->imageLocation( width ).url(); debug() << "MPRIS2: New location for width" << width << "is" << url; } if ( url.isValid() && url.isLocalFile() ) map["mpris:artUrl"] = QString::fromLatin1( url.toEncoded() ); map["xesam:album"] = album->name(); if ( album->hasAlbumArtist() ) map["xesam:albumArtist"] = QStringList() << album->albumArtist()->name(); } if( artist ) map["xesam:artist"] = QStringList() << artist->name(); const QString lyrics = track->cachedLyrics(); if( !lyrics.isEmpty() ) map["xesam:asText"] = lyrics; if( track->bpm() > 0 ) map["xesam:audioBPM"] = int(track->bpm()); map["xesam:autoRating"] = statistics->score(); map["xesam:comment"] = QStringList() << track->comment(); if( composer ) map["xesam:composer"] = QStringList() << composer->name(); if( year ) map["xesam:contentCreated"] = QDateTime(QDate(year->year(), 1, 1)).toString(Qt::ISODate); if( track->discNumber() ) map["xesam:discNumber"] = track->discNumber(); if( statistics->firstPlayed().isValid() ) map["xesam:firstUsed"] = statistics->firstPlayed().toString(Qt::ISODate); if( genre ) map["xesam:genre"] = QStringList() << genre->name(); if( statistics->lastPlayed().isValid() ) map["xesam:lastUsed"] = statistics->lastPlayed().toString(Qt::ISODate); map["xesam:title"] = track->prettyName(); map["xesam:trackNumber"] = track->trackNumber(); map["xesam:url"] = track->playableUrl().url(); map["xesam:useCount"] = statistics->playCount(); map["xesam:userRating"] = statistics->rating() / 10.; // xesam:userRating is a float } return map; } void Meta::Field::updateTrack( Meta::TrackPtr track, const QVariantMap &metadata ) { if( !track ) return; Meta::TrackEditorPtr ec = track->editor(); if( !ec ) return; ec->beginUpdate(); QString title = metadata.contains( Meta::Field::TITLE ) ? metadata.value( Meta::Field::TITLE ).toString() : QString(); ec->setTitle( title ); QString comment = metadata.contains( Meta::Field::COMMENT ) ? metadata.value( Meta::Field::COMMENT ).toString() : QString(); ec->setComment( comment ); int tracknr = metadata.contains( Meta::Field::TRACKNUMBER ) ? metadata.value( Meta::Field::TRACKNUMBER ).toInt() : 0; ec->setTrackNumber( tracknr ); int discnr = metadata.contains( Meta::Field::DISCNUMBER ) ? metadata.value( Meta::Field::DISCNUMBER ).toInt() : 0; ec->setDiscNumber( discnr ); QString artist = metadata.contains( Meta::Field::ARTIST ) ? metadata.value( Meta::Field::ARTIST ).toString() : QString(); ec->setArtist( artist ); QString album = metadata.contains( Meta::Field::ALBUM ) ? metadata.value( Meta::Field::ALBUM ).toString() : QString(); ec->setAlbum( album ); QString albumArtist = metadata.contains( Meta::Field::ALBUMARTIST ) ? metadata.value( Meta::Field::ALBUMARTIST ).toString() : QString(); QString genre = metadata.contains( Meta::Field::GENRE ) ? metadata.value( Meta::Field::GENRE ).toString() : QString(); ec->setGenre( genre ); QString composer = metadata.contains( Meta::Field::COMPOSER ) ? metadata.value( Meta::Field::COMPOSER ).toString() : QString(); ec->setComposer( composer ); int year = metadata.contains( Meta::Field::YEAR ) ? metadata.value( Meta::Field::YEAR ).toInt() : 0; ec->setYear( year ); ec->endUpdate(); } QString Meta::Field::xesamPrettyToFullFieldName( const QString &name ) { if( name == Meta::Field::ARTIST ) return XESAM_ARTIST; else if( name == Meta::Field::ALBUM ) return XESAM_ALBUM; else if( name == Meta::Field::ALBUMARTIST ) return XESAM_ALBUMARTIST; else if( name == Meta::Field::BITRATE ) return XESAM_BITRATE; else if( name == Meta::Field::BPM ) return XESAM_BPM; else if( name == Meta::Field::CODEC ) return XESAM_CODEC; else if( name == Meta::Field::COMMENT ) return XESAM_COMMENT; else if( name == Meta::Field::COMPOSER ) return XESAM_COMPOSER; else if( name == Meta::Field::DISCNUMBER ) return XESAM_DISCNUMBER; else if( name == Meta::Field::FILESIZE ) return XESAM_FILESIZE; else if( name == Meta::Field::GENRE ) return XESAM_GENRE; else if( name == Meta::Field::LENGTH ) return XESAM_LENGTH; else if( name == Meta::Field::RATING ) return XESAM_RATING; else if( name == Meta::Field::SAMPLERATE ) return XESAM_SAMPLERATE; else if( name == Meta::Field::TITLE ) return XESAM_TITLE; else if( name == Meta::Field::TRACKNUMBER ) return XESAM_TRACKNUMBER; else if( name == Meta::Field::URL ) return XESAM_URL; else if( name == Meta::Field::YEAR ) return XESAM_YEAR; else if( name==Meta::Field::SCORE ) return XESAM_SCORE; else if( name==Meta::Field::PLAYCOUNT ) return XESAM_PLAYCOUNT; else if( name==Meta::Field::FIRST_PLAYED ) return XESAM_FIRST_PLAYED; else if( name==Meta::Field::LAST_PLAYED ) return XESAM_LAST_PLAYED; else if( name==Meta::Field::UNIQUEID ) return XESAM_ID; else return "xesamPrettyToFullName: unknown name " + name; } QString Meta::Field::xesamFullToPrettyFieldName( const QString &name ) { if( name == XESAM_ARTIST ) return Meta::Field::ARTIST; if( name == XESAM_ALBUMARTIST ) return Meta::Field::ALBUMARTIST; else if( name == XESAM_ALBUM ) return Meta::Field::ALBUM; else if( name == XESAM_BITRATE ) return Meta::Field::BITRATE; else if( name == XESAM_BPM ) return Meta::Field::BPM; else if( name == XESAM_CODEC ) return Meta::Field::CODEC; else if( name == XESAM_COMMENT ) return Meta::Field::COMMENT; else if( name == XESAM_COMPOSER ) return Meta::Field::COMPOSER; else if( name == XESAM_DISCNUMBER ) return Meta::Field::DISCNUMBER; else if( name == XESAM_FILESIZE ) return Meta::Field::FILESIZE; else if( name == XESAM_GENRE ) return Meta::Field::GENRE; else if( name == XESAM_LENGTH ) return Meta::Field::LENGTH; else if( name == XESAM_RATING ) return Meta::Field::RATING; else if( name == XESAM_SAMPLERATE ) return Meta::Field::SAMPLERATE; else if( name == XESAM_TITLE ) return Meta::Field::TITLE; else if( name == XESAM_TRACKNUMBER ) return Meta::Field::TRACKNUMBER; else if( name == XESAM_URL ) return Meta::Field::URL; else if( name == XESAM_YEAR ) return Meta::Field::YEAR; else if( name == XESAM_SCORE ) return Meta::Field::SCORE; else if( name == XESAM_PLAYCOUNT ) return Meta::Field::PLAYCOUNT; else if( name == XESAM_FIRST_PLAYED ) return Meta::Field::FIRST_PLAYED; else if( name == XESAM_LAST_PLAYED ) return Meta::Field::LAST_PLAYED; else if( name == XESAM_ID ) return Meta::Field::UNIQUEID; else return "xesamFullToPrettyName: unknown name " + name; } QString Meta::msToPrettyTime( qint64 ms ) { return Meta::secToPrettyTime( ms / 1000 ); } QString Meta::secToPrettyTime( int seconds ) { if( seconds < 60 * 60 ) // one hour return QTime().addSecs( seconds ).toString( i18nc("the time format for a time length when the time is below 1 hour see QTime documentation.", "m:ss" ) ); // split days off for manual formatting (QTime doesn't work properly > 1 day, // QDateTime isn't suitable as it thinks it's a date) int days = seconds / 86400; seconds %= 86400; QString reply = ""; if ( days > 0 ) reply += i18ncp("number of days with spacing for the pretty time", "%1 day, ", "%1 days, ", days); reply += QTime().addSecs( seconds ).toString( i18nc("the time format for a time length when the time is 1 hour or above see QTime documentation.", "h:mm:ss" ) ); return reply; } QString Meta::secToPrettyTimeLong( int seconds ) { int minutes = seconds / 60; int hours = minutes / 60; int days = hours / 24; int months = days / 30; // a short month int years = months / 12; if( months > 24 || (((months % 12) == 0) && years > 0) ) return i18ncp("number of years for the pretty time", "%1 year", "%1 years", years); if( days > 60 || (((days % 30) == 0) && months > 0) ) return i18ncp("number of months for the pretty time", "%1 month", "%1 months", months); if( hours > 24 || (((hours % 24) == 0) && days > 0) ) return i18ncp("number of days for the pretty time", "%1 day", "%1 days", days); if( minutes > 120 || (((minutes % 60) == 0) && hours > 0) ) return i18ncp("number of hours for the pretty time", "%1 hour", "%1 hours", hours); if( seconds > 120 || (((seconds % 60) == 0) && minutes > 0) ) return i18ncp("number of minutes for the pretty time", "%1 minute", "%1 minutes", hours); return i18ncp("number of seconds for the pretty time", "%1 second", "%1 seconds", hours); } QString Meta::prettyFilesize( quint64 size ) { return KIO::convertSize( size ); } QString Meta::prettyBitrate( int bitrate ) { //the point here is to force sharing of these strings returned from prettyBitrate() static const QString bitrateStore[9] = { "?", "32", "64", "96", "128", "160", "192", "224", "256" }; return (bitrate >=0 && bitrate <= 256 && bitrate % 32 == 0) ? bitrateStore[ bitrate / 32 ] : QString( "%1" ).arg( bitrate ); } diff --git a/src/core/playlists/Playlist.h b/src/core/playlists/Playlist.h index d6eeb9633d..6f76ea4032 100644 --- a/src/core/playlists/Playlist.h +++ b/src/core/playlists/Playlist.h @@ -1,297 +1,297 @@ /**************************************************************************************** * Copyright (c) 2007 Bart Cerneels * * * * 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, see . * ****************************************************************************************/ #ifndef AMAROK_META_PLAYLIST_H #define AMAROK_META_PLAYLIST_H #include "core/amarokcore_export.h" #include "core/meta/forward_declarations.h" #include #include #include #include #include #include #include #include #include -#include +#include class QTextStream; class QAction; typedef QList QActionList; namespace Playlists { class Playlist; class PlaylistProvider; typedef KSharedPtr PlaylistPtr; typedef QList PlaylistList; enum PlaylistCategory { UserPlaylist = 1, PodcastChannelPlaylist }; /** * Subclass this class in order to be able to watch playlists as their metadata and * track list changes. */ class AMAROK_CORE_EXPORT PlaylistObserver { public: PlaylistObserver(); virtual ~PlaylistObserver(); /** * Subscribe to changes made by @param playlist. Does nothing if playlist is * null or if already subscribed. * * This method is thread-safe. */ void subscribeTo( PlaylistPtr playlist ); /** * Unsubscribe from changes made by @param playlist. Does nothing if not yet * subscribed to playlist. * * This method is thread-safe. */ void unsubscribeFrom( PlaylistPtr playlist ); /** * This method is called when playlist metadata (such as title) has changed. * This isn't called when just a list of tracks changes. * * @param playlist playlist whose metadata were changed * * @note this method may get called from non-main thread and must be * implemented in a thread-safe manner */ virtual void metadataChanged( PlaylistPtr playlist ); /** * This method is called when a track has been added to the playlist. * * @param playlist playlist whose track list was changed * @param track track that was added * @param position position where the track was inserted to, beginning from 0 * * @note this method may get called from non-main thread and must be * implemented in a thread-safe manner */ virtual void trackAdded( PlaylistPtr playlist, Meta::TrackPtr track, int position ); /** * This method is called after a track is removed from to the playlist. * * @param playlist playlist whose track list was changed * @param position position occupied by the track right before it was removed * * @note this method may get called from non-main thread and must be * implemented in a thread-safe manner */ virtual void trackRemoved( PlaylistPtr playlist, int position ); /** * This method is called after loading of playlist is finished * (which was started by triggerTrackLoad()) and all tracks are already added. * * @param playlist playlist loading of which has finished * * @note this method may get called from non-main thread and must be * implemented in a thread-safe manner */ virtual void tracksLoaded( PlaylistPtr playlist ); private: QSet m_playlistSubscriptions; QMutex m_playlistSubscriptionsMutex; // guards access to m_playlistSubscriptions }; class AMAROK_CORE_EXPORT Playlist : public virtual QSharedData { public: Playlist(); virtual ~Playlist(); /** * @returns a unique identifier for a playlist. Should be similar to * Meta::Track::uidUrl */ - virtual KUrl uidUrl() const = 0; + virtual QUrl uidUrl() const = 0; virtual QString name() const = 0; virtual QString prettyName() const { return name(); } virtual PlaylistProvider *provider() const { return 0; } virtual void setName( const QString &name ); /** * Returns the number of tracks this playlist contains. -1 if tracks are not * yet loaded (call triggerTrackLoad() in this case). If you get non-negative * number, all tracks have been already loaded. */ virtual int trackCount() const = 0; /** * Returns loaded tracks in this playlist. Note that the list may be incomplete, * to be sure, check that trackCount() is non-negative. Otherwise you have to * become playlist observer, watch for trackAdded() methods and call * triggerTrackLoad(). If you want to immediately play or * extract metadata of the tracks, be aware that many playlist implementations * initially return MetaProxy::Tracks that are resolved asynchronously. * * Convenient way to overcome the first and optionally the second * inconvenience is to use TrackLoader helper class. */ virtual Meta::TrackList tracks() = 0; /** * Trigger full background loading of this playlist. Observer's trackAdded() * and metadataChanged() will be called as appropriate. This may even change * playlist metadata; * * Implementors, you should start a background job in this method to * actually load tracks, calling notifyObservers[Something]Added/Changed() * as appropriate. * It is guaranteed that tracksLoaded() observer method will be called * exactly once, either sooner (before returning from this method) or * later (asynchronously perhaps from a different thread). * * Implementors should also use MetaProxy::Track as a second-level * lazy-loading. * * Default implementation just calls notifyObserversTracksLoaded(). */ virtual void triggerTrackLoad(); /** * Add the track to a certain position in the playlist * * @param position place to add this track. The default value -1 appends to * the end. * * @note if the position is larger then the size of the playlist append to the * end without generating an error. */ virtual void addTrack( Meta::TrackPtr track, int position = -1 ); /** * Remove track at the specified position */ virtual void removeTrack( int position ); /** * Sync track status between two tracks. This is only * useful for podcasts providers and some other exotic * playlist providers. */ virtual void syncTrackStatus( int position, Meta::TrackPtr otherTrack ); /** * A list of groups or labels this playlist belongs to. * * Can be used for grouping in folders (use ex. '/' as separator) or for * labels. Default implementation returns empty list. */ virtual QStringList groups(); /** * Labels the playlist as part of a group. * * In a folder-like hierarchy this means adding the playlist to the folder with * name groups.first(). If groups is empty that means removing all groups from * the playlist. Default implementation does nothing. */ virtual void setGroups( const QStringList &groups ); // FIXME: two methods below are a temporary solution // and should be removed after support of async loading will // added everywhere /** * Call this method to assure synchronously loading. * @note not all playlist implemetations support asynchronous loading */ KDE_DEPRECATED void makeLoadingSync() { m_async = false; } /** * Allows to check if asynchronously loading is deactivated */ bool isLoadingAsync() const { return m_async; } protected: /** * Implementations must call this when metadata such as title has changed. Do * not call this when just a list of track changes. * * @note calling this from (code called by) Playlist constructor is FORBIDDEN. * * TODO: find all occurrences where this should be called in Playlist subclasses * and add the call! */ void notifyObserversMetadataChanged(); /** * Implementations must call this when playlist loading started * by trriggerTrackLoad() is finished and all tracks are added. * * @note calling this from (code called by) Playlist constructor is FORBIDDEN. */ void notifyObserversTracksLoaded(); /** * Implementations must call this when a track is added to playlist * * @param position is the actual new position of the added track, never negative * @note calling this from (code called by) Playlist constructor is FORBIDDEN. */ void notifyObserversTrackAdded( const Meta::TrackPtr &track, int position ); /** * Implementations must call this when a track is added to playlist * * @param position is the position where the track was before removal * @note calling this from (code called by) Playlist constructor is FORBIDDEN. */ void notifyObserversTrackRemoved( int position ); private: friend class PlaylistObserver; // so that it can call (un)subscribe() void subscribe( PlaylistObserver *observer ); void unsubscribe( PlaylistObserver *observer ); QSet m_observers; /** * Guards access to m_observers. It would seem that QReadWriteLock would be * more efficient, but when it is locked for read, it cannot be relocked for * write, even if it is recursive. This can cause deadlocks, so it would be * never safe to lock it just for read. */ QMutex m_observersMutex; bool m_async; }; } Q_DECLARE_METATYPE( Playlists::PlaylistPtr ) Q_DECLARE_METATYPE( Playlists::PlaylistList ) #endif diff --git a/src/core/playlists/PlaylistFormat.cpp b/src/core/playlists/PlaylistFormat.cpp index f88e142cf5..a154eaf747 100644 --- a/src/core/playlists/PlaylistFormat.cpp +++ b/src/core/playlists/PlaylistFormat.cpp @@ -1,52 +1,52 @@ /**************************************************************************************** * Copyright (c) 2007 Ian Monroe * * (c) 2010 Jeff Mitchell * * * * 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) version 3 or * * any later version accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * 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, see . * ****************************************************************************************/ #include "core/playlists/PlaylistFormat.h" #include "core/support/Amarok.h" -#include +#include #include namespace Playlists { PlaylistFormat -getFormat( const KUrl &path ) +getFormat( const QUrl &path ) { const QString ext = Amarok::extension( path.fileName() ); if( ext == "m3u" || ext == "m3u8" ) return M3U; //m3u8 is M3U in UTF8 if( ext == "pls" ) return PLS; if( ext == "ram" ) return RAM; if( ext == "smil") return SMIL; if( ext == "asx" || ext == "wax" || ext == "asf" ) return ASX; if( ext == "xml" ) return XML; if( ext == "xspf" ) return XSPF; return Unknown; } bool -isPlaylist( const KUrl &path ) +isPlaylist( const QUrl &path ) { return ( getFormat( path ) != Unknown ); } } diff --git a/src/core/playlists/PlaylistFormat.h b/src/core/playlists/PlaylistFormat.h index 8e0e022d11..d6f129c2dd 100644 --- a/src/core/playlists/PlaylistFormat.h +++ b/src/core/playlists/PlaylistFormat.h @@ -1,46 +1,46 @@ /**************************************************************************************** * Copyright (c) 2007 Ian Monroe * * (c) 2010 Jeff Mitchell * * * * 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, see . * ****************************************************************************************/ #ifndef AMAROK_PLAYLISTFORMAT_H #define AMAROK_PLAYLISTFORMAT_H #include "core/amarokcore_export.h" -#include +#include class QFile; namespace Playlists { enum PlaylistFormat { M3U, PLS, XML, RAM, SMIL, ASX, XSPF, Unknown, NotPlaylist = Unknown }; - AMAROK_CORE_EXPORT PlaylistFormat getFormat( const KUrl &path ); - AMAROK_CORE_EXPORT bool isPlaylist( const KUrl &path ); + AMAROK_CORE_EXPORT PlaylistFormat getFormat( const QUrl &path ); + AMAROK_CORE_EXPORT bool isPlaylist( const QUrl &path ); } #endif diff --git a/src/core/podcasts/PodcastImageFetcher.cpp b/src/core/podcasts/PodcastImageFetcher.cpp index dc3927ccaf..215c5068c8 100644 --- a/src/core/podcasts/PodcastImageFetcher.cpp +++ b/src/core/podcasts/PodcastImageFetcher.cpp @@ -1,151 +1,152 @@ /**************************************************************************************** * Copyright (c) 2009 Bart Cerneels * * * * 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, see . * ****************************************************************************************/ #include "core/podcasts/PodcastImageFetcher.h" #include "core/support/Debug.h" #include #include #include PodcastImageFetcher::PodcastImageFetcher() { } void PodcastImageFetcher::addChannel( Podcasts::PodcastChannelPtr channel ) { DEBUG_BLOCK if( channel->imageUrl().isEmpty() ) { debug() << channel->title() << " does not have an imageUrl"; return; } if( hasCachedImage( channel ) ) { debug() << "using cached image for " << channel->title(); QString imagePath = cachedImagePath( channel ).toLocalFile(); QImage image( imagePath ); if( image.isNull() ) error() << "could not load pixmap from " << imagePath; else channel->setImage( image ); return; } debug() << "Adding " << channel->title() << " to fetch queue"; m_channels.append( channel ); } void PodcastImageFetcher::addEpisode( Podcasts::PodcastEpisodePtr episode ) { Q_UNUSED( episode ); } -KUrl +QUrl PodcastImageFetcher::cachedImagePath( Podcasts::PodcastChannelPtr channel ) { return cachedImagePath( channel.data() ); } -KUrl +QUrl PodcastImageFetcher::cachedImagePath( Podcasts::PodcastChannel *channel ) { - KUrl imagePath = channel->saveLocation(); + QUrl imagePath = channel->saveLocation(); if( imagePath.isEmpty() ) imagePath = Amarok::saveLocation( "podcasts" ); KMD5 md5( channel->url().url().toLocal8Bit() ); QString extension = Amarok::extension( channel->imageUrl().fileName() ); - imagePath.addPath( md5.hexDigest() + '.' + extension ); + imagePath = imagePath.adjusted(QUrl::StripTrailingSlash); + imagePath.setPath(imagePath.path() + '/' + ( md5.hexDigest() + '.' + extension )); return imagePath.toLocalFile(); } bool PodcastImageFetcher::hasCachedImage( Podcasts::PodcastChannelPtr channel ) { DEBUG_BLOCK return QFile( PodcastImageFetcher::cachedImagePath( Podcasts::PodcastChannelPtr::dynamicCast( channel ) ).toLocalFile() ).exists(); } void PodcastImageFetcher::run() { if( m_channels.isEmpty() && m_episodes.isEmpty() && m_jobChannelMap.isEmpty() && m_jobEpisodeMap.isEmpty() ) { //nothing to do emit( done( this ) ); return; } if( Solid::Networking::status() != Solid::Networking::Connected && Solid::Networking::status() != Solid::Networking::Unknown ) { debug() << "Solid reports we are not online, canceling podcast image download"; emit( done( this ) ); //TODO: schedule another run after Solid reports we are online again return; } foreach( Podcasts::PodcastChannelPtr channel, m_channels ) { - KUrl cachedPath = cachedImagePath( channel ); - KIO::mkdir( cachedPath.directory() ); + QUrl cachedPath = cachedImagePath( channel ); + KIO::mkdir( cachedPath.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path() ); KIO::FileCopyJob *job = KIO::file_copy( channel->imageUrl(), cachedPath, -1, KIO::HideProgressInfo | KIO::Overwrite ); //remove channel from the todo list m_channels.removeAll( channel ); m_jobChannelMap.insert( job, channel ); connect( job, SIGNAL(finished(KJob*)), SLOT(slotDownloadFinished(KJob*)) ); } //TODO: episodes } void PodcastImageFetcher::slotDownloadFinished( KJob *job ) { DEBUG_BLOCK //QMap::take() also removes the entry from the map. Podcasts::PodcastChannelPtr channel = m_jobChannelMap.take( job ); if( channel.isNull() ) { error() << "got null PodcastChannelPtr " << __FILE__ << ":" << __LINE__; return; } if( job->error() ) { error() << "downloading podcast image " << job->errorString(); } else { QString imagePath = cachedImagePath( channel ).toLocalFile(); QImage image( imagePath ); if( image.isNull() ) error() << "could not load pixmap from " << imagePath; else channel->setImage( image ); } //call run again to start the next batch of transfers. run(); } diff --git a/src/core/podcasts/PodcastImageFetcher.h b/src/core/podcasts/PodcastImageFetcher.h index ac1aba9c77..a9b968f2b5 100644 --- a/src/core/podcasts/PodcastImageFetcher.h +++ b/src/core/podcasts/PodcastImageFetcher.h @@ -1,55 +1,55 @@ /**************************************************************************************** * Copyright (c) 2009 Bart Cerneels * * * * 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, see . * ****************************************************************************************/ #ifndef PODCASTIMAGEFETCHER_H #define PODCASTIMAGEFETCHER_H #include "core/podcasts/PodcastMeta.h" #include class AMAROK_CORE_EXPORT PodcastImageFetcher : public QObject { Q_OBJECT public: PodcastImageFetcher(); void addChannel( Podcasts::PodcastChannelPtr channel ); void addEpisode( Podcasts::PodcastEpisodePtr episode ); void run(); - static KUrl cachedImagePath( Podcasts::PodcastChannelPtr channel ); - static KUrl cachedImagePath( Podcasts::PodcastChannel *channel ); + static QUrl cachedImagePath( Podcasts::PodcastChannelPtr channel ); + static QUrl cachedImagePath( Podcasts::PodcastChannel *channel ); signals: void imageReady( Podcasts::PodcastChannelPtr channel, QImage image ); void imageReady( Podcasts::PodcastEpisodePtr episode, QImage image ); void done( PodcastImageFetcher * ); private slots: void slotDownloadFinished( KJob *job ); private: static bool hasCachedImage( Podcasts::PodcastChannelPtr channel ); Podcasts::PodcastChannelList m_channels; Podcasts::PodcastEpisodeList m_episodes; QMap m_jobChannelMap; QMap m_jobEpisodeMap; }; #endif // PODCASTIMAGEFETCHER_H diff --git a/src/core/podcasts/PodcastMeta.h b/src/core/podcasts/PodcastMeta.h index d7cdff7455..fc101c5854 100644 --- a/src/core/podcasts/PodcastMeta.h +++ b/src/core/podcasts/PodcastMeta.h @@ -1,467 +1,467 @@ /**************************************************************************************** * Copyright (c) 2007-2009 Bart Cerneels * * * * 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, see . * ****************************************************************************************/ #ifndef PODCASTMETA_H #define PODCASTMETA_H #include "core/amarokcore_export.h" #include "core/meta/Meta.h" #include "core/playlists/Playlist.h" #include "core/support/Amarok.h" #include -#include +#include #include #include #include #include #include namespace Podcasts { class PodcastEpisode; class PodcastChannel; class PodcastArtist; class PodcastAlbum; class PodcastComposer; class PodcastGenre; class PodcastYear; typedef KSharedPtr PodcastEpisodePtr; typedef KSharedPtr PodcastChannelPtr; typedef QList PodcastEpisodeList; typedef QList PodcastChannelList; class AMAROK_CORE_EXPORT PodcastMetaCommon { public: PodcastMetaCommon() {} virtual ~PodcastMetaCommon() {} virtual QString title() const { return m_title;} virtual QString description() const { return m_description; } virtual QStringList keywords() const { return m_keywords; } virtual QString subtitle() const { return m_subtitle; } virtual QString summary() const { return m_summary; } virtual QString author() const { return m_author; } virtual void setTitle( const QString &title ) { m_title = title; } virtual void setDescription( const QString &description ) { m_description = description; } virtual void setKeywords( const QStringList &keywords ) { m_keywords = keywords; } virtual void addKeyword( const QString &keyword ) { m_keywords << keyword; } virtual void setSubtitle( const QString &subtitle ) { m_subtitle = subtitle; } virtual void setSummary( const QString &summary ) { m_summary = summary; } virtual void setAuthor( const QString &author ) { m_author = author; } protected: QString m_title; //the title QString m_description; //a longer description, with HTML markup QStringList m_keywords; // TODO: save to DB QString m_subtitle; //a short description QString m_summary; QString m_author; // TODO: save to DB }; class AMAROK_CORE_EXPORT PodcastEpisode : public PodcastMetaCommon, public Meta::Track { public: PodcastEpisode(); PodcastEpisode( PodcastChannelPtr channel ); PodcastEpisode( PodcastEpisodePtr episode, PodcastChannelPtr channel ); virtual ~PodcastEpisode() {} // Meta::Base methods virtual QString name() const { return m_title; } // Meta::Track Methods - virtual KUrl playableUrl() const { return m_localUrl.isEmpty() ? m_url : m_localUrl; } + virtual QUrl playableUrl() const { return m_localUrl.isEmpty() ? m_url : m_localUrl; } virtual QString prettyUrl() const { return playableUrl().prettyUrl(); } virtual QString uidUrl() const { return m_url.url(); } virtual QString notPlayableReason() const; virtual Meta::AlbumPtr album() const { return m_albumPtr; } virtual Meta::ArtistPtr artist() const { return m_artistPtr; } virtual Meta::ComposerPtr composer() const { return m_composerPtr; } virtual Meta::GenrePtr genre() const { return m_genrePtr; } virtual Meta::YearPtr year() const { return m_yearPtr; } virtual qreal bpm() const { return -1.0; } virtual QString comment() const { return QString(); } virtual void setComment( const QString &newComment ) { Q_UNUSED( newComment ); } virtual qint64 length() const { return m_duration * 1000; } virtual int filesize() const { return m_fileSize; } virtual int sampleRate() const { return 0; } virtual int bitrate() const { return 0; } virtual int trackNumber() const { return m_sequenceNumber; } virtual void setTrackNumber( int newTrackNumber ) { Q_UNUSED( newTrackNumber ); } virtual int discNumber() const { return 0; } virtual void setDiscNumber( int newDiscNumber ) { Q_UNUSED( newDiscNumber ); } virtual QString mimeType() const { return m_mimeType; } virtual QString type() const { const QString fileName = playableUrl().fileName(); return Amarok::extension( fileName ); } virtual void addMatchTo( Collections::QueryMaker* qm ) { Q_UNUSED( qm ); } virtual bool inCollection() const { return false; } virtual QString cachedLyrics() const { return QString(); } virtual void setCachedLyrics( const QString &lyrics ) { Q_UNUSED( lyrics ); } virtual bool operator==( const Meta::Track &track ) const; //PodcastMetaCommon methods virtual void setTitle( const QString &title ) { m_title = title; } //PodcastEpisode methods - virtual KUrl localUrl() const { return m_localUrl; } + virtual QUrl localUrl() const { return m_localUrl; } virtual QDateTime pubDate() const { return m_pubDate; } virtual int duration() const { return m_duration; } virtual QString guid() const { return m_guid; } virtual bool isNew() const { return m_isNew; } virtual int sequenceNumber() const { return m_sequenceNumber; } virtual PodcastChannelPtr channel() const { return m_channel; } - virtual void setLocalUrl( const KUrl &url ) { m_localUrl = url; } + virtual void setLocalUrl( const QUrl &url ) { m_localUrl = url; } virtual void setFilesize( int fileSize ) { m_fileSize = fileSize; } virtual void setMimeType( const QString &mimeType ) { m_mimeType = mimeType; } - virtual void setUidUrl( const KUrl &url ) { m_url = url; } + virtual void setUidUrl( const QUrl &url ) { m_url = url; } virtual void setPubDate( const QDateTime &pubDate ) { m_pubDate = pubDate; } virtual void setDuration( int duration ) { m_duration = duration; } virtual void setGuid( const QString &guid ) { m_guid = guid; } virtual void setNew( bool isNew ) { m_isNew = isNew; } virtual void setSequenceNumber( int sequenceNumber ) { m_sequenceNumber = sequenceNumber; } virtual void setChannel( const PodcastChannelPtr channel ) { m_channel = channel; } protected: PodcastChannelPtr m_channel; QString m_guid; //the GUID from the podcast feed - KUrl m_url; //remote url of the file - KUrl m_localUrl; //the localUrl, only valid if downloaded + QUrl m_url; //remote url of the file + QUrl m_localUrl; //the localUrl, only valid if downloaded QString m_mimeType; //the mimetype of the enclosure QDateTime m_pubDate; //the pubDate from the feed int m_duration; //the playlength in seconds int m_fileSize; //the size tag from the enclosure int m_sequenceNumber; //number of the episode bool m_isNew; //listened to or not? //data members Meta::AlbumPtr m_albumPtr; Meta::ArtistPtr m_artistPtr; Meta::ComposerPtr m_composerPtr; Meta::GenrePtr m_genrePtr; Meta::YearPtr m_yearPtr; }; class AMAROK_CORE_EXPORT PodcastChannel : public PodcastMetaCommon, public Playlists::Playlist { public: enum FetchType { DownloadWhenAvailable = 0, StreamOrDownloadOnDemand }; PodcastChannel() : PodcastMetaCommon() , Playlist() , m_image() , m_subscribeDate() , m_copyright() , m_autoScan( false ) , m_fetchType( DownloadWhenAvailable ) , m_purge( false ) , m_purgeCount( 0 ) { } PodcastChannel( Podcasts::PodcastChannelPtr channel ); virtual ~PodcastChannel() {} //Playlist virtual methods - virtual KUrl uidUrl() const { return m_url; } + virtual QUrl uidUrl() const { return m_url; } virtual QString name() const { return title(); } virtual int trackCount() const { return m_episodes.count(); } virtual Meta::TrackList tracks(); virtual void addTrack( Meta::TrackPtr track, int position = -1 ); //PodcastMetaCommon methods // override this since it's ambigous in PodcastMetaCommon and Playlist virtual QString description() const { return m_description; } //PodcastChannel methods - virtual KUrl url() const { return m_url; } - virtual KUrl webLink() const { return m_webLink; } + virtual QUrl url() const { return m_url; } + virtual QUrl webLink() const { return m_webLink; } virtual bool hasImage() const { return !m_image.isNull(); } - virtual KUrl imageUrl() const { return m_imageUrl; } + virtual QUrl imageUrl() const { return m_imageUrl; } virtual QImage image() const { return m_image; } virtual QString copyright() const { return m_copyright; } virtual QStringList labels() const { return m_labels; } virtual QDate subscribeDate() const { return m_subscribeDate; } - virtual void setUrl( const KUrl &url ) { m_url = url; } - virtual void setWebLink( const KUrl &link ) { m_webLink = link; } + virtual void setUrl( const QUrl &url ) { m_url = url; } + virtual void setWebLink( const QUrl &link ) { m_webLink = link; } // TODO: inform all albums with this channel of the changed image virtual void setImage( const QImage &image ) { m_image = image; } - virtual void setImageUrl( const KUrl &imageUrl ) { m_imageUrl = imageUrl; } + virtual void setImageUrl( const QUrl &imageUrl ) { m_imageUrl = imageUrl; } virtual void setCopyright( const QString ©right ) { m_copyright = copyright; } virtual void setLabels( const QStringList &labels ) { m_labels = labels; } virtual void addLabel( const QString &label ) { m_labels << label; } virtual void setSubscribeDate( const QDate &date ) { m_subscribeDate = date; } virtual Podcasts::PodcastEpisodePtr addEpisode( PodcastEpisodePtr episode ); virtual PodcastEpisodeList episodes() const { return m_episodes; } bool load( QTextStream &stream ) { Q_UNUSED( stream ); return false; } //PodcastChannel Settings - KUrl saveLocation() const { return m_directory; } + QUrl saveLocation() const { return m_directory; } bool autoScan() const { return m_autoScan; } FetchType fetchType() const { return m_fetchType; } bool hasPurge() const { return m_purge; } int purgeCount() const { return m_purgeCount; } - void setSaveLocation( const KUrl &url ) { m_directory = url; } + void setSaveLocation( const QUrl &url ) { m_directory = url; } void setAutoScan( bool autoScan ) { m_autoScan = autoScan; } void setFetchType( FetchType fetchType ) { m_fetchType = fetchType; } void setPurge( bool purge ) { m_purge = purge; } void setPurgeCount( int purgeCount ) { m_purgeCount = purgeCount; } protected: - KUrl m_url; - KUrl m_webLink; + QUrl m_url; + QUrl m_webLink; QImage m_image; - KUrl m_imageUrl; + QUrl m_imageUrl; QStringList m_labels; QDate m_subscribeDate; QString m_copyright; - KUrl m_directory; //the local directory to save the files in. + QUrl m_directory; //the local directory to save the files in. bool m_autoScan; //should this channel be checked automatically? PodcastChannel::FetchType m_fetchType; //'download when available' or 'stream or download on demand' bool m_purge; //remove old episodes? int m_purgeCount; //how many episodes do we keep on disk? PodcastEpisodeList m_episodes; }; // internal helper classes class AMAROK_CORE_EXPORT PodcastArtist : public Meta::Artist { public: PodcastArtist( PodcastEpisode *episode ) : Meta::Artist() , episode( episode ) {} Meta::TrackList tracks() { return Meta::TrackList(); } Meta::AlbumList albums() { return Meta::AlbumList(); } QString name() const { QString author; if( episode && episode->channel() ) author = episode->channel()->author(); return author; } bool operator==( const Meta::Artist &other ) const { return name() == other.name(); } PodcastEpisode const *episode; }; class AMAROK_CORE_EXPORT PodcastAlbum : public Meta::Album { public: PodcastAlbum( PodcastEpisode *episode ) : Meta::Album() , episode( episode ) {} /* Its all a little bit stupid. When the cannel image (and also the album image) changes the album get's no indication. Also the CoverCache is not in amarokcorelib but in amaroklib. Why the PodcastAlbum is the only one with a concrete implementation in amarokcorelib is another question. virtual ~PodcastAlbum() { CoverCache::invalidateAlbum( Meta::AlbumPtr(this) ); } */ bool isCompilation() const { return false; } bool hasAlbumArtist() const { return false; } Meta::ArtistPtr albumArtist() const { return Meta::ArtistPtr(); } Meta::TrackList tracks() { return Meta::TrackList(); } QString name() const { if( episode != 0 ) { const QString albumName = episode->channel()->title(); return albumName; } else return QString(); } QImage image( int size ) const { // This is a little stupid. If Channel::setImage is called we don't emit a MetaDataChanged or invalidate the cache QImage image = episode->channel()->image(); return image.scaledToHeight( size ); } bool operator==( const Meta::Album &other ) const { return name() == other.name(); } PodcastEpisode const *episode; }; class AMAROK_CORE_EXPORT PodcastGenre : public Meta::Genre { public: PodcastGenre( PodcastEpisode *episode ) : Meta::Genre() , episode( episode ) {} Meta::TrackList tracks() { return Meta::TrackList(); } QString name() const { const QString genreName = i18n( "Podcast" ); return genreName; } bool operator==( const Meta::Genre &other ) const { return name() == other.name(); } PodcastEpisode const *episode; }; class AMAROK_CORE_EXPORT PodcastComposer : public Meta::Composer { public: PodcastComposer( PodcastEpisode *episode ) : Meta::Composer() , episode( episode ) {} Meta::TrackList tracks() { return Meta::TrackList(); } QString name() const { if( episode != 0 ) { const QString composer = episode->channel()->author(); return composer; } else return QString(); } bool operator==( const Meta::Composer &other ) const { return name() == other.name(); } PodcastEpisode const *episode; }; class AMAROK_CORE_EXPORT PodcastYear : public Meta::Year { public: PodcastYear( PodcastEpisode *episode ) : Meta::Year() , episode( episode ) {} Meta::TrackList tracks() { return Meta::TrackList(); } QString name() const { if( episode != 0 ) { const QString year = episode->pubDate().toString( "yyyy" ); return year; } else return QString(); } bool operator==( const Meta::Year &other ) const { return name() == other.name(); } PodcastEpisode const *episode; }; } //namespace Podcasts Q_DECLARE_METATYPE( Podcasts::PodcastMetaCommon* ) Q_DECLARE_METATYPE( Podcasts::PodcastEpisodePtr ) Q_DECLARE_METATYPE( Podcasts::PodcastEpisodeList ) Q_DECLARE_METATYPE( Podcasts::PodcastChannelPtr ) Q_DECLARE_METATYPE( Podcasts::PodcastChannelList ) #endif diff --git a/src/core/podcasts/PodcastProvider.cpp b/src/core/podcasts/PodcastProvider.cpp index a346fe2fbf..fe19fef758 100644 --- a/src/core/podcasts/PodcastProvider.cpp +++ b/src/core/podcasts/PodcastProvider.cpp @@ -1,88 +1,88 @@ /**************************************************************************************** * Copyright (c) 2009 Bart Cerneels * * * * 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, see . * ****************************************************************************************/ #include "core/podcasts/PodcastProvider.h" #include "core/support/Debug.h" -#include +#include using namespace Podcasts; bool PodcastProvider::couldBeFeed( const QString &urlString ) { DEBUG_BLOCK QStringList feedProtocols; feedProtocols << "itpc"; feedProtocols << "pcast"; feedProtocols << "feed"; QString matchString = QString( "^(%1)" ).arg( feedProtocols.join( "|" ) ); QRegExp rx( matchString ); int pos = rx.indexIn( urlString.trimmed() ); return pos != -1; } -KUrl +QUrl PodcastProvider::toFeedUrl( const QString &urlString ) { DEBUG_BLOCK debug() << urlString; - KUrl kurl( urlString.trimmed() ); + QUrl kurl( urlString.trimmed() ); - if( kurl.protocol() == "itpc" ) + if( kurl.scheme() == "itpc" ) { debug() << "itpc:// url."; - kurl.setProtocol( "http" ); + kurl.setScheme( "http" ); } - else if( kurl.protocol() == "pcast" ) + else if( kurl.scheme() == "pcast" ) { debug() << "pcast:// url."; - kurl.setProtocol( "http" ); + kurl.setScheme( "http" ); } - else if( kurl.protocol() == "feed" ) + else if( kurl.scheme() == "feed" ) { //TODO: also handle the case feed:https://example.com/entries.atom debug() << "feed:// url."; - kurl.setProtocol( "http" ); + kurl.setScheme( "http" ); } return kurl; } Playlists::PlaylistPtr PodcastProvider::addPlaylist( Playlists::PlaylistPtr playlist ) { PodcastChannelPtr channel = PodcastChannelPtr::dynamicCast( playlist ); if( channel.isNull() ) return Playlists::PlaylistPtr(); return Playlists::PlaylistPtr::dynamicCast( addChannel( channel ) ); } Meta::TrackPtr PodcastProvider::addTrack( Meta::TrackPtr track ) { PodcastEpisodePtr episode = PodcastEpisodePtr::dynamicCast( track ); if( episode.isNull() ) return Meta::TrackPtr(); return Meta::TrackPtr::dynamicCast( addEpisode( episode ) ); } diff --git a/src/core/podcasts/PodcastProvider.h b/src/core/podcasts/PodcastProvider.h index f126ebeae8..106e67c2df 100644 --- a/src/core/podcasts/PodcastProvider.h +++ b/src/core/podcasts/PodcastProvider.h @@ -1,72 +1,72 @@ /**************************************************************************************** * Copyright (c) 2007-2009 Bart Cerneels * * * * 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, see . * ****************************************************************************************/ #ifndef PODCASTPROVIDER_H #define PODCASTPROVIDER_H #include "core/collections/Collection.h" #include "core/playlists/PlaylistProvider.h" #include "core/podcasts/PodcastMeta.h" namespace Podcasts { /** @author Bart Cerneels */ class AMAROK_CORE_EXPORT PodcastProvider : public Collections::TrackProvider, public Playlists::PlaylistProvider { public: static bool couldBeFeed( const QString &urlString ); - static KUrl toFeedUrl( const QString &urlString ); + static QUrl toFeedUrl( const QString &urlString ); - virtual bool possiblyContainsTrack( const KUrl &url ) const = 0; - virtual Meta::TrackPtr trackForUrl( const KUrl &url ) = 0; + virtual bool possiblyContainsTrack( const QUrl &url ) const = 0; + virtual Meta::TrackPtr trackForUrl( const QUrl &url ) = 0; /** Special function to get an episode for a given guid. * - * note: this functions is required because KUrl does not preserve every possible guids. + * note: this functions is required because QUrl does not preserve every possible guids. * This means we can not use trackForUrl(). * Problematic guids contain non-latin characters, percent encoded parts, capitals, etc. */ virtual Podcasts::PodcastEpisodePtr episodeForGuid( const QString &guid ) = 0; - virtual void addPodcast( const KUrl &url ) = 0; + virtual void addPodcast( const QUrl &url ) = 0; virtual void updateAll() {} virtual Podcasts::PodcastChannelPtr addChannel( Podcasts::PodcastChannelPtr channel ) = 0; virtual Podcasts::PodcastEpisodePtr addEpisode( Podcasts::PodcastEpisodePtr episode ) = 0; virtual Podcasts::PodcastChannelList channels() = 0; //TODO: need to move this to SqlPodcastProvider since it's provider specific. //perhaps use a more general transferprogress for playlists virtual void completePodcastDownloads() = 0; // PlaylistProvider methods virtual int category() const { return Playlists::PodcastChannelPlaylist; } /** convenience function that downcast the argument to PodcastChannel and calls addChannel() */ virtual Playlists::PlaylistPtr addPlaylist( Playlists::PlaylistPtr playlist ); /** convenience function that downcast the argument to PodcastEpisode and calls addEpisode() */ virtual Meta::TrackPtr addTrack( Meta::TrackPtr track ); }; } //namespace Podcasts #endif diff --git a/src/core/podcasts/PodcastReader.cpp b/src/core/podcasts/PodcastReader.cpp index bcb114a3e9..49db753ae2 100644 --- a/src/core/podcasts/PodcastReader.cpp +++ b/src/core/podcasts/PodcastReader.cpp @@ -1,1693 +1,1693 @@ /**************************************************************************************** * Copyright (c) 2007 Bart Cerneels * * 2009 Mathias Panzenböck * * 2013 Ralf Engels * * * * 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, see . * ****************************************************************************************/ #include "core/podcasts/PodcastReader.h" #include "core/support/Amarok.h" #include "core/support/Components.h" #include "core/support/Debug.h" #include "core/meta/support/MetaUtility.h" #include -#include +#include #include #include #include #include using namespace Podcasts; #define ITUNES_NS "http://www.itunes.com/dtds/podcast-1.0.dtd" #define RDF_NS "http://www.w3.org/1999/02/22-rdf-syntax-ns#" #define RSS10_NS "http://purl.org/rss/1.0/" #define RSS20_NS "" #define ATOM_NS "http://www.w3.org/2005/Atom" #define ENC_NS "http://purl.oclc.org/net/rss_2.0/enc#" #define CONTENT_NS "http://purl.org/rss/1.0/modules/content" #define DC_NS "http://purl.org/dc/elements/1.1/" // regular expressions for linkification: #define RE_USER "[-+_%\\.\\w]+" #define RE_PASSWD RE_USER #define RE_DOMAIN "[-a-zA-Z0-9]+(?:\\.[-a-zA-Z0-9]+)*" #define RE_PROT "[a-zA-Z]+://" #define RE_URL RE_PROT "(?:" RE_USER "(?::" RE_PASSWD ")?@)?" RE_DOMAIN \ "(?::\\d+)?(?:/[-\\w\\?&=%+.,;:_#~/!@]*)?" #define RE_MAIL RE_USER "@" RE_DOMAIN const PodcastReader::StaticData PodcastReader::sd; PodcastReader::PodcastReader( PodcastProvider *podcastProvider, QObject *parent ) : QObject( parent ) , m_xmlReader() , m_podcastProvider( podcastProvider ) , m_transferJob( 0 ) , m_current( 0 ) , m_actionStack() , m_contentType( TextContent ) , m_buffer() {} void PodcastReader::Action::begin( PodcastReader *podcastReader ) const { if( m_begin ) (( *podcastReader ).*m_begin )(); } void PodcastReader::Action::end( PodcastReader *podcastReader ) const { if( m_end ) (( *podcastReader ).*m_end )(); } void PodcastReader::Action::characters( PodcastReader *podcastReader ) const { if( m_characters ) (( *podcastReader ).*m_characters )(); } // initialization of the feed parser automata: PodcastReader::StaticData::StaticData() : removeScripts( "|]*>", Qt::CaseInsensitive ) , mightBeHtml( "<\\?xml[^>]*\\?>|]*>|]*>|<|>|&|"|" "<([-:\\w\\d]+)[^>]*(/>|>.*)|]*>|&#\\d+;|&#x[a-fA-F\\d]+;", Qt::CaseInsensitive ) , linkify( "\\b(" RE_URL ")|\\b(" RE_MAIL ")|(\n)" ) , startAction( rootMap ) , docAction( docMap, 0, &PodcastReader::endDocument ) , xmlAction( xmlMap, &PodcastReader::beginXml, &PodcastReader::endXml, &PodcastReader::readEscapedCharacters ) , skipAction( skipMap ) , noContentAction( noContentMap, &PodcastReader::beginNoElement, 0, &PodcastReader::readNoCharacters ) , rdfAction( rdfMap, &PodcastReader::beginRdf ) , rssAction( rssMap, &PodcastReader::beginRss ) , feedAction( feedMap, &PodcastReader::beginFeed ) , htmlAction( skipMap, &PodcastReader::beginHtml ) , unknownFeedTypeAction( skipMap, &PodcastReader::beginUnknownFeedType ) // RSS 1.0+2.0 , rss10ChannelAction( rss10ChannelMap, &PodcastReader::beginChannel ) , rss20ChannelAction( rss20ChannelMap, &PodcastReader::beginChannel ) , titleAction( textMap, &PodcastReader::beginText, &PodcastReader::endTitle, &PodcastReader::readCharacters ) , subtitleAction( textMap, &PodcastReader::beginText, &PodcastReader::endSubtitle, &PodcastReader::readCharacters ) , descriptionAction( textMap, &PodcastReader::beginText, &PodcastReader::endDescription, &PodcastReader::readCharacters ) , encodedAction( textMap, &PodcastReader::beginText, &PodcastReader::endEncoded, &PodcastReader::readCharacters ) , bodyAction( xmlMap, &PodcastReader::beginText, &PodcastReader::endBody, &PodcastReader::readEscapedCharacters ) , linkAction( textMap, &PodcastReader::beginText, &PodcastReader::endLink, &PodcastReader::readCharacters ) , imageAction( imageMap, &PodcastReader::beginImage ) , itemAction( itemMap, &PodcastReader::beginItem, &PodcastReader::endItem ) , urlAction( textMap, &PodcastReader::beginText, &PodcastReader::endImageUrl, &PodcastReader::readCharacters ) , authorAction( textMap, &PodcastReader::beginText, &PodcastReader::endAuthor, &PodcastReader::readCharacters ) , creatorAction( textMap, &PodcastReader::beginText, &PodcastReader::endCreator, &PodcastReader::readCharacters ) , enclosureAction( noContentMap, &PodcastReader::beginEnclosure ) , guidAction( textMap, &PodcastReader::beginText, &PodcastReader::endGuid, &PodcastReader::readCharacters ) , pubDateAction( textMap, &PodcastReader::beginText, &PodcastReader::endPubDate, &PodcastReader::readCharacters ) , keywordsAction( textMap, &PodcastReader::beginText, &PodcastReader::endKeywords, &PodcastReader::readCharacters ) , newFeedUrlAction( textMap, &PodcastReader::beginText, &PodcastReader::endNewFeedUrl, &PodcastReader::readCharacters ) // Atom , atomLogoAction( textMap, &PodcastReader::beginText, &PodcastReader::endImageUrl, &PodcastReader::readCharacters ) , atomIconAction( textMap, &PodcastReader::beginText, &PodcastReader::endAtomIcon, &PodcastReader::readCharacters ) , atomEntryAction( atomEntryMap, &PodcastReader::beginItem, &PodcastReader::endItem ) , atomTitleAction( atomTextMap, &PodcastReader::beginAtomText, &PodcastReader::endAtomTitle, &PodcastReader::readAtomTextCharacters ) , atomSubtitleAction( atomTextMap, &PodcastReader::beginAtomText, &PodcastReader::endAtomSubtitle, &PodcastReader::readAtomTextCharacters ) , atomAuthorAction( atomAuthorMap ) , atomFeedLinkAction( noContentMap, &PodcastReader::beginAtomFeedLink, 0, &PodcastReader::readNoCharacters ) , atomEntryLinkAction( noContentMap, &PodcastReader::beginAtomEntryLink, 0, &PodcastReader::readNoCharacters ) , atomIdAction( textMap, &PodcastReader::beginText, &PodcastReader::endGuid, &PodcastReader::readCharacters ) , atomPublishedAction( textMap, &PodcastReader::beginText, &PodcastReader::endAtomPublished, &PodcastReader::readCharacters ) , atomUpdatedAction( textMap, &PodcastReader::beginText, &PodcastReader::endAtomUpdated, &PodcastReader::readCharacters ) , atomSummaryAction( atomTextMap, &PodcastReader::beginAtomText, &PodcastReader::endAtomSummary, &PodcastReader::readAtomTextCharacters ) , atomContentAction( atomTextMap, &PodcastReader::beginAtomText, &PodcastReader::endAtomContent, &PodcastReader::readAtomTextCharacters ) , atomTextAction( atomTextMap, &PodcastReader::beginAtomTextChild, &PodcastReader::endAtomTextChild, &PodcastReader::readAtomTextCharacters ) { // known elements: knownElements[ "rss" ] = Rss; knownElements[ "RDF" ] = Rdf; knownElements[ "feed" ] = Feed; knownElements[ "channel" ] = Channel; knownElements[ "item" ] = Item; knownElements[ "image" ] = Image; knownElements[ "link" ] = Link; knownElements[ "url" ] = Url; knownElements[ "title" ] = Title; knownElements[ "author" ] = Author; knownElements[ "enclosure" ] = EnclosureElement; knownElements[ "guid" ] = Guid; knownElements[ "pubDate" ] = PubDate; knownElements[ "description" ] = Description; knownElements[ "summary" ] = Summary; knownElements[ "body" ] = Body; knownElements[ "entry" ] = Entry; knownElements[ "content" ] = Content; knownElements[ "name" ] = Name; knownElements[ "id" ] = Id; knownElements[ "subtitle" ] = Subtitle; knownElements[ "updated" ] = Updated; knownElements[ "published" ] = Published; knownElements[ "logo" ] = Logo; knownElements[ "icon" ] = Icon; knownElements[ "encoded" ] = Encoded; knownElements[ "creator" ] = Creator; knownElements[ "keywords" ] = Keywords; knownElements[ "new-feed-url" ] = NewFeedUrl; knownElements[ "html" ] = Html; knownElements[ "HTML" ] = Html; // before start document/after end document rootMap.insert( Document, &docAction ); // parse document docMap.insert( Rss, &rssAction ); docMap.insert( Html, &htmlAction ); docMap.insert( Rdf, &rdfAction ); docMap.insert( Feed, &feedAction ); docMap.insert( Any, &unknownFeedTypeAction ); // parse "RSS 2.0" rssMap.insert( Channel, &rss20ChannelAction ); // parse "RSS 1.0" rdfMap.insert( Channel, &rss10ChannelAction ); rdfMap.insert( Item, &itemAction ); // parse "RSS 2.0" rss20ChannelMap.insert( Title, &titleAction ); rss20ChannelMap.insert( ItunesSubtitle, &subtitleAction ); rss20ChannelMap.insert( ItunesAuthor, &authorAction ); rss20ChannelMap.insert( Creator, &creatorAction ); rss20ChannelMap.insert( Description, &descriptionAction ); rss20ChannelMap.insert( Encoded, &encodedAction ); rss20ChannelMap.insert( ItunesSummary, &descriptionAction ); rss20ChannelMap.insert( Body, &bodyAction ); rss20ChannelMap.insert( Link, &linkAction ); rss20ChannelMap.insert( Image, &imageAction ); rss20ChannelMap.insert( ItunesKeywords, &keywordsAction ); rss20ChannelMap.insert( NewFeedUrl, &newFeedUrlAction ); rss20ChannelMap.insert( Item, &itemAction ); // parse "RSS 1.0" rss10ChannelMap.insert( Title, &titleAction ); rss10ChannelMap.insert( ItunesSubtitle, &subtitleAction ); rss10ChannelMap.insert( ItunesAuthor, &authorAction ); rss10ChannelMap.insert( Creator, &creatorAction ); rss10ChannelMap.insert( Description, &descriptionAction ); rss10ChannelMap.insert( Encoded, &encodedAction ); rss10ChannelMap.insert( ItunesSummary, &descriptionAction ); rss10ChannelMap.insert( Body, &bodyAction ); rss10ChannelMap.insert( Link, &linkAction ); rss10ChannelMap.insert( Image, &imageAction ); rss10ChannelMap.insert( ItunesKeywords, &keywordsAction ); rss10ChannelMap.insert( NewFeedUrl, &newFeedUrlAction ); // parse imageMap.insert( Title, &skipAction ); imageMap.insert( Link, &skipAction ); imageMap.insert( Url, &urlAction ); // parse itemMap.insert( Title, &titleAction ); itemMap.insert( ItunesSubtitle, &subtitleAction ); itemMap.insert( Author, &authorAction ); itemMap.insert( ItunesAuthor, &authorAction ); itemMap.insert( Creator, &creatorAction ); itemMap.insert( Description, &descriptionAction ); itemMap.insert( Encoded, &encodedAction ); itemMap.insert( ItunesSummary, &descriptionAction ); itemMap.insert( Body, &bodyAction ); itemMap.insert( EnclosureElement, &enclosureAction ); itemMap.insert( Guid, &guidAction ); itemMap.insert( PubDate, &pubDateAction ); itemMap.insert( ItunesKeywords, &keywordsAction ); // TODO: move the link field from PodcastChannel to PodcastMetaCommon // itemMap.insert( Link, &linkAction ); // parse "Atom" feedMap.insert( Title, &atomTitleAction ); feedMap.insert( Subtitle, &atomSubtitleAction ); feedMap.insert( Icon, &atomIconAction ); feedMap.insert( Logo, &atomLogoAction ); feedMap.insert( Author, &atomAuthorAction ); feedMap.insert( Link, &atomFeedLinkAction ); feedMap.insert( Entry, &atomEntryAction ); // parse "Atom" atomEntryMap.insert( Title, &atomTitleAction ); atomEntryMap.insert( Subtitle, &atomSubtitleAction ); atomEntryMap.insert( Author, &atomAuthorAction ); atomEntryMap.insert( Id, &atomIdAction ); atomEntryMap.insert( Published, &atomPublishedAction ); atomEntryMap.insert( Updated, &atomUpdatedAction ); atomEntryMap.insert( Summary, &atomSummaryAction ); atomEntryMap.insert( Link, &atomEntryLinkAction ); atomEntryMap.insert( SupportedContent, &atomContentAction ); // parse "Atom" atomAuthorMap.insert( Name, &authorAction ); // parse atom text atomTextMap.insert( Any, &atomTextAction ); // parse arbitrary xml xmlMap.insert( Any, &xmlAction ); // skip elements skipMap.insert( Any, &skipAction ); } PodcastReader::~PodcastReader() { DEBUG_BLOCK } bool PodcastReader::mightBeHtml( const QString& text ) //Static { return sd.mightBeHtml.indexIn( text ) != -1; } bool PodcastReader::read( QIODevice *device ) { DEBUG_BLOCK m_xmlReader.setDevice( device ); return read(); } bool -PodcastReader::read( const KUrl &url ) +PodcastReader::read( const QUrl &url ) { DEBUG_BLOCK m_url = url; m_transferJob = KIO::get( m_url, KIO::Reload, KIO::HideProgressInfo ); connect( m_transferJob, SIGNAL(data(KIO::Job*,QByteArray)), SLOT(slotAddData(KIO::Job*,QByteArray)) ); connect( m_transferJob, SIGNAL(result(KJob*)), SLOT(downloadResult(KJob*)) ); - connect( m_transferJob, SIGNAL(redirection(KIO::Job*,KUrl)), - SLOT(slotRedirection(KIO::Job*,KUrl)) ); + connect( m_transferJob, SIGNAL(redirection(KIO::Job*,QUrl)), + SLOT(slotRedirection(KIO::Job*,QUrl)) ); connect( m_transferJob, SIGNAL( permanentRedirection( KIO::Job *, - const KUrl &, const KUrl & ) ), - SLOT( slotPermanentRedirection( KIO::Job *, const KUrl &, - const KUrl & ) ) ); + const QUrl &, const QUrl & ) ), + SLOT( slotPermanentRedirection( KIO::Job *, const QUrl &, + const QUrl & ) ) ); QString description = i18n( "Importing podcast channel from %1", url.url() ); if( m_channel ) { description = m_channel->title().isEmpty() ? i18n( "Updating podcast channel" ) : i18n( "Updating \"%1\"", m_channel->title() ); } emit statusBarNewProgressOperation( m_transferJob, description, this ); // parse data return read(); } void PodcastReader::slotAbort() { DEBUG_BLOCK } bool PodcastReader::update( PodcastChannelPtr channel ) { DEBUG_BLOCK m_channel = channel; return read( m_channel->url() ); } void PodcastReader::slotAddData( KIO::Job *job, const QByteArray &data ) { DEBUG_BLOCK Q_UNUSED( job ) m_xmlReader.addData( data ); // parse more data continueRead(); } void PodcastReader::downloadResult( KJob * job ) { DEBUG_BLOCK // parse more data continueRead(); KIO::TransferJob *transferJob = dynamic_cast( job ); if( transferJob && transferJob->isErrorPage() ) { QString errorMessage = i18n( "Importing podcast from %1 failed with error:\n", m_url.url() ); if( m_channel ) { errorMessage = m_channel->title().isEmpty() ? i18n( "Updating podcast from %1 failed with error:\n", m_url.url() ) : i18n( "Updating \"%1\" failed with error:\n", m_channel->title() ); } errorMessage = errorMessage.append( job->errorString() ); emit statusBarSorryMessage( errorMessage ); } else if( job->error() ) { QString errorMessage = i18n( "Importing podcast from %1 failed with error:\n", m_url.url() ); if( m_channel ) { errorMessage = m_channel->title().isEmpty() ? i18n( "Updating podcast from %1 failed with error:\n", m_url.url() ) : i18n( "Updating \"%1\" failed with error:\n", m_channel->title() ); } errorMessage = errorMessage.append( job->errorString() ); emit statusBarSorryMessage( errorMessage ); } m_transferJob = 0; } PodcastReader::ElementType PodcastReader::elementType() const { if( m_xmlReader.isEndDocument() || m_xmlReader.isStartDocument() ) return Document; if( m_xmlReader.isCDATA() || m_xmlReader.isCharacters() ) return CharacterData; ElementType elementType = sd.knownElements[ m_xmlReader.name().toString()]; // This is a bit hacky because my automata does not support conditions. // Therefore I put the decision logic in here and declare some pseudo elements. // I don't think it is worth it to extend the automata to support such conditions. switch( elementType ) { case Summary: if( m_xmlReader.namespaceUri() == ITUNES_NS ) { elementType = ItunesSummary; } break; case Subtitle: if( m_xmlReader.namespaceUri() == ITUNES_NS ) { elementType = ItunesSubtitle; } break; case Author: if( m_xmlReader.namespaceUri() == ITUNES_NS ) { elementType = ItunesAuthor; } break; case Keywords: if( m_xmlReader.namespaceUri() == ITUNES_NS ) { elementType = ItunesKeywords; } break; case Content: if( m_xmlReader.namespaceUri() == ATOM_NS && // ignore atom:content elements that do not // have content but only refer to some url: !hasAttribute( ATOM_NS, "src" ) ) { // Atom supports arbitrary Base64 encoded content. // Because we can only something with text/html/xhtml I ignore // anything else. // See: // http://tools.ietf.org/html/rfc4287#section-4.1.3 if( hasAttribute( ATOM_NS, "type" ) ) { QStringRef type( attribute( ATOM_NS, "type" ) ); if( type == "text" || type == "html" || type == "xhtml" ) { elementType = SupportedContent; } } else { elementType = SupportedContent; } } break; default: break; } return elementType; } bool PodcastReader::read() { DEBUG_BLOCK m_current = 0; m_item = 0; m_contentType = TextContent; m_buffer.clear(); m_actionStack.clear(); m_actionStack.push( &( PodcastReader::sd.startAction ) ); m_xmlReader.setNamespaceProcessing( true ); return continueRead(); } bool PodcastReader::continueRead() { // this is some kind of pushdown automata // with this it should be possible to parse feeds in parallel // woithout using threads DEBUG_BLOCK while( !m_xmlReader.atEnd() && m_xmlReader.error() != QXmlStreamReader::CustomError ) { QXmlStreamReader::TokenType token = m_xmlReader.readNext(); if( m_xmlReader.error() == QXmlStreamReader::PrematureEndOfDocumentError && m_transferJob ) { return true; } if( m_xmlReader.hasError() ) { emit finished( this ); return false; } if( m_actionStack.isEmpty() ) { debug() << "expected element on stack!"; return false; } const Action* action = m_actionStack.top(); const Action* subAction = 0; switch( token ) { case QXmlStreamReader::Invalid: return false; case QXmlStreamReader::StartDocument: case QXmlStreamReader::StartElement: subAction = action->actionMap()[ elementType()]; if( !subAction ) subAction = action->actionMap()[ Any ]; if( !subAction ) subAction = &( PodcastReader::sd.skipAction ); m_actionStack.push( subAction ); subAction->begin( this ); break; case QXmlStreamReader::EndDocument: case QXmlStreamReader::EndElement: action->end( this ); if( m_actionStack.pop() != action ) { debug() << "popped other element than expected!"; } break; case QXmlStreamReader::Characters: if( !m_xmlReader.isWhitespace() || m_xmlReader.isCDATA() ) { action->characters( this ); } // ignoreable whitespaces case QXmlStreamReader::Comment: case QXmlStreamReader::EntityReference: case QXmlStreamReader::ProcessingInstruction: case QXmlStreamReader::DTD: case QXmlStreamReader::NoToken: // ignore break; } } return !m_xmlReader.hasError(); } void PodcastReader::stopWithError( const QString &message ) { m_xmlReader.raiseError( message ); if( m_transferJob ) { m_transferJob->kill(KJob::EmitResult); m_transferJob = 0; } emit finished( this ); } void PodcastReader::beginText() { m_buffer.clear(); } void PodcastReader::endTitle() { m_current->setTitle( m_buffer.trimmed() ); } void PodcastReader::endSubtitle() { m_current->setSubtitle( m_buffer.trimmed() ); } QString PodcastReader::atomTextAsText() { switch( m_contentType ) { case HtmlContent: case XHtmlContent: // TODO: strip tags (there should not be any non-xml entities here) return unescape( m_buffer ); case TextContent: default: return m_buffer; } } QString PodcastReader::atomTextAsHtml() { switch( m_contentType ) { case HtmlContent: case XHtmlContent: // strip