diff --git a/src/App.h b/src/App.h index a2fe990e15..d8dbe9034d 100644 --- a/src/App.h +++ b/src/App.h @@ -1,99 +1,99 @@ /**************************************************************************************** * 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 "amarok_export.h" #include #include #include namespace Amarok { class TrayIcon; } namespace ScriptConsoleNS{ class ScriptConsole; } namespace KIO { class Job; } class MediaDeviceManager; class MainWindow; class QCommandLineParser; class QUrl; class AMAROK_EXPORT App : public QApplication { Q_OBJECT public: App(int &argc, char **argv); ~App(); static App *instance() { return static_cast( qApp ); } void continueInit(); Amarok::TrayIcon* trayIcon() const { return m_tray; } void handleCliArgs(const QString &cwd); void initCliArgs(QCommandLineParser *parsers); virtual int newInstance(); inline QPointer mainWindow() const { return m_mainWindow; } Q_SIGNALS: void prepareToQuit(); void settingsChanged(); public Q_SLOTS: void activateRequested(const QStringList & arguments, const QString & cwd); void applySettings(); void applySettingsFirstTime(); void slotConfigAmarok( const QString& page = QString() ); void slotConfigAmarokWithEmptyPage(); void slotConfigShortcuts(); KIO::Job *trashFiles( const QList &files ); void quit(); protected: virtual bool event( QEvent *event ); private Q_SLOTS: void slotTrashResult( KJob *job ); private: void handleFirstRun(); // ATTRIBUTES QPointer m_mainWindow; Amarok::TrayIcon *m_tray; MediaDeviceManager *m_mediaDeviceManager; QPointer m_scriptConsole; QCommandLineParser *m_args; QString m_cwd; QStringList s_delayedAmarokUrls; }; -#define pApp static_cast(QCoreApplication::instance()) +#define pApp App::instance() #endif // AMAROK_APP_H diff --git a/src/GlobalCollectionActions.h b/src/GlobalCollectionActions.h index 894f012b29..7e95417015 100644 --- a/src/GlobalCollectionActions.h +++ b/src/GlobalCollectionActions.h @@ -1,168 +1,167 @@ /**************************************************************************************** * 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 . * ****************************************************************************************/ #ifndef GLOBALCOLLECTIONACTIONS_H #define GLOBALCOLLECTIONACTIONS_H #include "amarok_export.h" #include "core/meta/forward_declarations.h" -#include "core/support/SmartPointerList.h" #include class AMAROK_EXPORT GlobalCollectionAction : public QAction { public: GlobalCollectionAction( const QString &text, QObject * parent ); }; class AMAROK_EXPORT GlobalCollectionGenreAction : public GlobalCollectionAction { public: GlobalCollectionGenreAction( const QString &text, QObject * parent ); void setGenre( Meta::GenrePtr genre ); protected: Meta::GenrePtr genre(); private: Meta::GenrePtr m_currentGenre; }; class AMAROK_EXPORT GlobalCollectionArtistAction : public GlobalCollectionAction { public: GlobalCollectionArtistAction( const QString &text, QObject * parent ); void setArtist( Meta::ArtistPtr artist ); protected: Meta::ArtistPtr artist(); private: Meta::ArtistPtr m_currentArtist; }; class AMAROK_EXPORT GlobalCollectionAlbumAction : public GlobalCollectionAction { public: GlobalCollectionAlbumAction( const QString &text, QObject * parent ); void setAlbum( Meta::AlbumPtr album ); protected: Meta::AlbumPtr album(); private: Meta::AlbumPtr m_currentAlbum; }; class AMAROK_EXPORT GlobalCollectionTrackAction : public GlobalCollectionAction { public: GlobalCollectionTrackAction( const QString &text, QObject * parent ); void setTrack( Meta::TrackPtr track ); protected: Meta::TrackPtr track(); private: Meta::TrackPtr m_currentTrack; }; class AMAROK_EXPORT GlobalCollectionYearAction : public GlobalCollectionAction { public: GlobalCollectionYearAction( const QString &text, QObject * parent ); void setYear( Meta::YearPtr year ); protected: Meta::YearPtr year(); private: Meta::YearPtr m_currentYear; }; class GlobalCollectionComposerAction : public GlobalCollectionAction { public: GlobalCollectionComposerAction( const QString &text, QObject * parent ); void setComposer( Meta::ComposerPtr composer ); protected: Meta::ComposerPtr composer(); private: Meta::ComposerPtr m_currentComposer; }; class GlobalCollectionActions; namespace The { AMAROK_EXPORT GlobalCollectionActions* globalCollectionActions(); } /** This class keeps track of global context actions that should be added to all genre, artists or another meta type in all collections. */ class AMAROK_EXPORT GlobalCollectionActions : public QObject { Q_OBJECT friend GlobalCollectionActions* The::globalCollectionActions(); public: QList actionsFor( Meta::DataPtr item ); void addGenreAction( GlobalCollectionGenreAction * action ); void addArtistAction( GlobalCollectionArtistAction * action ); void addAlbumAction( GlobalCollectionAlbumAction * action ); void addTrackAction( GlobalCollectionTrackAction * action ); void addYearAction( GlobalCollectionYearAction * action ); void addComposerAction( GlobalCollectionComposerAction * action ); private: GlobalCollectionActions(); ~GlobalCollectionActions(); QList actionsFor( Meta::GenrePtr genre ); QList actionsFor( Meta::ArtistPtr artist ); QList actionsFor( Meta::AlbumPtr album ); QList actionsFor( Meta::TrackPtr track ); QList actionsFor( Meta::YearPtr year ); QList actionsFor( Meta::ComposerPtr composer ); - SmartPointerList m_genreActions; - SmartPointerList m_artistActions; - SmartPointerList m_albumActions; - SmartPointerList m_trackActions; - SmartPointerList m_yearActions; - SmartPointerList m_composerActions; + QList m_genreActions; + QList m_artistActions; + QList m_albumActions; + QList m_trackActions; + QList m_yearActions; + QList m_composerActions; }; #endif diff --git a/src/GlobalCurrentTrackActions.cpp b/src/GlobalCurrentTrackActions.cpp index d770bc85ce..c8561a9fbe 100644 --- a/src/GlobalCurrentTrackActions.cpp +++ b/src/GlobalCurrentTrackActions.cpp @@ -1,60 +1,53 @@ /**************************************************************************************** * 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 "GlobalCurrentTrackActions.h" #include namespace The { static GlobalCurrentTrackActions* s_GlobalCurrentTrackActions_instance = 0; GlobalCurrentTrackActions* globalCurrentTrackActions() { if( !s_GlobalCurrentTrackActions_instance ) s_GlobalCurrentTrackActions_instance = new GlobalCurrentTrackActions(); return s_GlobalCurrentTrackActions_instance; } } GlobalCurrentTrackActions::GlobalCurrentTrackActions() {} - GlobalCurrentTrackActions::~GlobalCurrentTrackActions() {} void GlobalCurrentTrackActions::addAction( QAction * action ) { m_actions.append( action ); + QObject::connect( action, &QObject::destroyed, this, [this, action]() { m_actions.removeAll( action ); } ); } QList< QAction * > GlobalCurrentTrackActions::actions() { - // Here we filter out dangling pointers to already destroyed QActions - - QList validActions; - - foreach( QAction* action, m_actions ) - validActions.append( action ); - - return validActions; + return m_actions; } diff --git a/src/GlobalCurrentTrackActions.h b/src/GlobalCurrentTrackActions.h index c756bd0928..4b8c8bb0dd 100644 --- a/src/GlobalCurrentTrackActions.h +++ b/src/GlobalCurrentTrackActions.h @@ -1,51 +1,53 @@ /**************************************************************************************** * 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 GLOBALCURRENTTRACKACTIONS_H #define GLOBALCURRENTTRACKACTIONS_H #include "amarok_export.h" #include "core/meta/forward_declarations.h" -#include "core/support/SmartPointerList.h" + +#include + class GlobalCurrentTrackActions; class QAction; namespace The { AMAROK_EXPORT GlobalCurrentTrackActions* globalCurrentTrackActions(); } /** A global list of actions that is made available to all playing tracks. @author Nikolaj Hald Nielsen */ -class AMAROK_EXPORT GlobalCurrentTrackActions +class AMAROK_EXPORT GlobalCurrentTrackActions : public QObject { friend GlobalCurrentTrackActions* The::globalCurrentTrackActions(); public: void addAction( QAction * action ); QList actions(); private: GlobalCurrentTrackActions(); ~GlobalCurrentTrackActions(); - SmartPointerList m_actions; + QList m_actions; }; #endif diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 36b5e3c8e4..497c9537f7 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -1,1389 +1,1387 @@ /**************************************************************************************** * 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 "App.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/BrowserDock.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/PlaylistDock.h" #include "playlist/PlaylistModelStack.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 //qApp #include #include #include #include #include #include //openPlaylist() #include //slotAddStream() #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_WS_X11 #include #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; -QPointer MainWindow::s_instance; - namespace The { - MainWindow* mainWindow() { return MainWindow::s_instance.data(); } + MainWindow* mainWindow() { return pApp->mainWindow(); } } 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->list()->navigate( path ); setAutoSaveSettings(); m_showMenuBar->setChecked(!menuBar()->isHidden()); // workaround for bug #171080 EngineController *engine = The::engineController(); connect( engine, &EngineController::stopped, this, &MainWindow::slotStopped ); connect( engine, &EngineController::paused, this, &MainWindow::slotPaused ); connect( engine, &EngineController::trackPlaying, this, &MainWindow::slotNewTrackPlaying ); connect( engine, &EngineController::trackMetadataChanged, this, &MainWindow::slotMetadataChanged ); } MainWindow::~MainWindow() { DEBUG_BLOCK //save currently active category Amarok::config().writeEntry( "Browser Path", m_browserDock->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( this ); 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( this ); m_slimToolbar.data()->setAllowedAreas( Qt::TopToolBarArea | Qt::BottomToolBarArea ); m_slimToolbar.data()->setMovable ( true ); addToolBar( Qt::TopToolBarArea, m_slimToolbar.data() ); m_slimToolbar->hide(); //BEGIN Creating Widgets PERF_LOG( "Create sidebar" ) m_browserDock = new BrowserDock( this ); m_browserDock->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Ignored ); m_browserDock->installEventFilter( this ); PERF_LOG( "Sidebar created" ) PERF_LOG( "Create Playlist" ) m_playlistDock = new Playlist::Dock( this ); m_playlistDock->installEventFilter( this ); //HACK, need to connect after because of order in MainWindow() connect( Amarok::actionCollection()->action( "playlist_edit_queue" ), &QAction::triggered, m_playlistDock.data(), &Playlist::Dock::slotEditQueue ); PERF_LOG( "Playlist created" ) PERF_LOG( "Creating ContextWidget" ) m_contextDock = new ContextDock( this ); m_contextDock->installEventFilter( this ); PERF_LOG( "ContextScene created" ) //END Creating Widgets createMenus(); 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( QIcon::fromTheme( "drive-harddisk" ) ); m_collectionBrowser->setShortDescription( i18n( "Local sources of content" ) ); m_browserDock->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( QIcon::fromTheme( "applications-internet" ) ); serviceBrowser->setShortDescription( i18n( "Online sources of content" ) ); m_browserDock->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( QIcon::fromTheme( "view-media-playlist-amarok" ) ); m_playlistBrowser->setShortDescription( i18n( "Various types of playlists" ) ); m_browserDock->list()->addCategory( m_playlistBrowser ); PERF_LOG( "CreatedPlaylsitBrowser" ) PERF_LOG( "Creating FileBrowser" ) FileBrowser *fileBrowser = new FileBrowser( "files", 0 ); fileBrowser->setPrettyName( i18n("Files") ); fileBrowser->setIcon( QIcon::fromTheme( "folder-amarok" ) ); fileBrowser->setShortDescription( i18n( "Browse local hard drive for content" ) ); m_browserDock->list()->addCategory( fileBrowser ); PERF_LOG( "Created FileBrowser" ) serviceBrowser->setScriptableServiceManager( The::scriptableServiceManager() ); PERF_LOG( "ScriptableServiceManager done" ) PERF_LOG( "Creating Podcast Category" ) m_browserDock->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, &MainWindow::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, &QAction::toggled, this, &MainWindow::setLayoutLocked ); menu->addAction( lockAction ); menu->addSeparator(); // Dock widgets: QList dockwidgets = findChildren(); foreach( QDockWidget* dockWidget, dockwidgets ) { if( dockWidget->parentWidget() == this ) menu->addAction( dockWidget->toggleViewAction() ); } menu->addSeparator(); // Toolbars: QList toolbars = findChildren(); QActionGroup* toolBarGroup = new QActionGroup( this ); toolBarGroup->setExclusive( true ); foreach( QToolBar* toolBar, toolbars ) { if( toolBar->parentWidget() == this ) { QAction* action = toolBar->toggleViewAction(); connect( action, &QAction::toggled, toolBar, &QToolBar::setVisible ); toolBarGroup->addAction( action ); menu->addAction( action ); } } menu->addSeparator(); QAction *resetAction = new QAction( i18n( "Reset Layout" ), this ); connect( resetAction, &QAction::triggered, this, &MainWindow::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->windowTitle(); break; case AmarokDockContext: name = m_contextDock->windowTitle(); break; case AmarokDockPlaylist: name = m_playlistDock->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() && !qApp->isSavingSession() ) { 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(); pApp->quit(); #endif } void MainWindow::exportPlaylist() //SLOT { DEBUG_BLOCK QFileDialog fileDialog; fileDialog.restoreState( Amarok::config( "playlist-export-dialog" ).readEntry( "state", QByteArray() ) ); // FIXME: Make checkbox visible in dialog QCheckBox *saveRelativeCheck = new QCheckBox( i18n("Use relative path for &saving"), &fileDialog ); 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.setMimeTypeFilters( supportedMimeTypes ); fileDialog.setAcceptMode( QFileDialog::AcceptSave ); fileDialog.setFileMode( QFileDialog::AnyFile ); fileDialog.setWindowTitle( i18n("Save As") ); fileDialog.setObjectName( "PlaylistExport" ); int result = fileDialog.exec(); QString playlistPath = fileDialog.selectedFiles().value( 0 ); if( result == QDialog::Accepted && !playlistPath.isEmpty() ) The::playlist()->exportPlaylist( playlistPath, saveRelativeCheck->isChecked() ); Amarok::config( "playlist-export-dialog" ).writeEntry( "state", fileDialog.saveState() ); } void MainWindow::slotShowActiveTrack() const { m_playlistDock->showActiveTrack(); } void MainWindow::slotEditTrackInfo() const { m_playlistDock->editTrackInfo(); } void MainWindow::slotShowCoverManager() //SLOT { CoverManager::showOnce( QString(), this ); } void MainWindow::slotShowDiagnosticsDialog() { DiagnosticDialog *dialog = new DiagnosticDialog( KAboutData::applicationData(), 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 QUrl lastDirectory; // open a file selector to add media to the playlist QList files; QFileDialog dlg; dlg.setDirectory( QStandardPaths::writableLocation(QStandardPaths::MusicLocation) ); if( !lastDirectory.isEmpty() ) dlg.setDirectoryUrl( lastDirectory ); dlg.setWindowTitle( directPlay ? i18n("Play Media (Files or URLs)") : i18n("Add Media (Files or URLs)") ); dlg.setFileMode( QFileDialog::ExistingFiles ); dlg.setObjectName( "PlayMedia" ); int accepted = dlg.exec(); files = dlg.selectedUrls(); lastDirectory = dlg.directoryUrl(); if( accepted != QDialog::Accepted || files.isEmpty() ) return; Playlist::AddOptions options = directPlay ? Playlist::OnPlayMediaAction : Playlist::OnAppendToPlaylistAction; The::playlistController()->insertOptioned( files, options ); } void MainWindow::slotAddStream() //SLOT { bool ok; QString url = QInputDialog::getText( this, i18n( "Add Stream" ), i18n( "Enter Stream URL:" ), QLineEdit::Normal, QString(), &ok ); if( !ok ) return; The::playlistController()->insertOptioned( QUrl( url ), Playlist::OnAppendToPlaylistAction | Playlist::RemotePlaylistsAreStreams ); } void MainWindow::slotFocusPlaylistSearch() { showDock( AmarokDockPlaylist ); // ensure that the dock is visible if tabbed m_playlistDock->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( 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->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( pApp, &App::slotConfigShortcuts, ac ); KStandardAction::preferences( pApp, &App::slotConfigAmarokWithEmptyPage, ac ); m_showMenuBar = KStandardAction::showMenubar(this, &MainWindow::slotShowMenuBar, ac); ac->action( KStandardAction::name( KStandardAction::KeyBindings ) )->setIcon( QIcon::fromTheme( "configure-shortcuts-amarok" ) ); ac->action( KStandardAction::name( KStandardAction::Preferences ) )->setIcon( QIcon::fromTheme( "configure-amarok" ) ); ac->action( KStandardAction::name( KStandardAction::Preferences ) )->setMenuRole(QAction::PreferencesRole); // Define OS X Prefs menu here, removes need for ifdef later KStandardAction::quit( pApp, &App::quit, ac ); QAction *action = new QAction( QIcon::fromTheme( "document-open" ), i18n("&Add Media..."), this ); ac->addAction( "playlist_add", action ); connect( action, &QAction::triggered, this, &MainWindow::slotAddLocation ); KGlobalAccel::setGlobalShortcut(action, QKeySequence( Qt::META + Qt::Key_A ) ); action = new QAction( QIcon::fromTheme( "edit-clear-list" ), i18nc( "clear playlist", "&Clear Playlist" ), this ); connect( action, &QAction::triggered, pc, &Playlist::Controller::clear ); ac->addAction( "playlist_clear", action ); action = new QAction( QIcon::fromTheme( "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( QKeySequence( Qt::META + Qt::Key_U ) ); ac->addAction( "playlist_edit_queue", action );; action = new QAction( i18nc( "Remove duplicate and dead (unplayable) tracks from the playlist", "Re&move Duplicates" ), this ); connect( action, &QAction::triggered, pc, &Playlist::Controller::removeDeadAndDuplicates ); ac->addAction( "playlist_remove_dead_and_duplicates", action ); action = new Playlist::LayoutConfigAction( this ); ac->addAction( "playlist_layout", action ); action = new QAction( QIcon::fromTheme( "document-open-remote" ), i18n("&Add Stream..."), this ); connect( action, &QAction::triggered, this, &MainWindow::slotAddStream ); ac->addAction( "stream_add", action ); action = new QAction( QIcon::fromTheme( "document-export-amarok" ), i18n("&Export Playlist As..."), this ); connect( action, &QAction::triggered, this, &MainWindow::exportPlaylist ); ac->addAction( "playlist_export", action ); action = new QAction( QIcon::fromTheme( "bookmark-new" ), i18n( "Bookmark Media Sources View" ), this ); ac->addAction( "bookmark_browser", action ); connect( action, &QAction::triggered, The::amarokUrlHandler(), &AmarokUrlHandler::bookmarkCurrentBrowserView ); action = new QAction( QIcon::fromTheme( "bookmarks-organize" ), i18n( "Bookmark Manager" ), this ); ac->addAction( "bookmark_manager", action ); connect( action, &QAction::triggered, this, &MainWindow::slotShowBookmarkManager ); action = new QAction( QIcon::fromTheme( "view-media-equalizer" ), i18n( "Equalizer" ), this ); ac->addAction( "equalizer_dialog", action ); connect( action, &QAction::triggered, this, &MainWindow::slotShowEqualizer ); action = new QAction( QIcon::fromTheme( "bookmark-new" ), i18n( "Bookmark Playlist Setup" ), this ); ac->addAction( "bookmark_playlistview", action ); connect( action, &QAction::triggered, The::amarokUrlHandler(), &AmarokUrlHandler::bookmarkCurrentPlaylistView ); action = new QAction( QIcon::fromTheme( "bookmark-new" ), i18n( "Bookmark Context Applets" ), this ); ac->addAction( "bookmark_contextview", action ); connect( action, &QAction::triggered, The::amarokUrlHandler(), &AmarokUrlHandler::bookmarkCurrentContextView ); action = new QAction( QIcon::fromTheme( "media-album-cover-manager-amarok" ), i18n( "Cover Manager" ), this ); connect( action, &QAction::triggered, this, &MainWindow::slotShowCoverManager ); ac->addAction( "cover_manager", action ); action = new QAction( QIcon::fromTheme("document-open"), i18n("Play Media..."), this ); ac->addAction( "playlist_playmedia", action ); action->setShortcut( Qt::CTRL + Qt::Key_O ); connect( action, &QAction::triggered, this, &MainWindow::slotPlayMedia ); action = new QAction( QIcon::fromTheme("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, &QAction::triggered, this, &MainWindow::slotEditTrackInfo ); action = new QAction( QIcon::fromTheme( "media-seek-forward-amarok" ), i18n( "Seek Forward by %1 seconds", KFormat().formatDecimalDuration( AmarokConfig::seekShort() * 1000 ) ), this ); ac->addAction( "seek_forward_short", action ); action->setShortcut( Qt::CTRL + Qt::Key_Right ); connect( action, &QAction::triggered, this, &MainWindow::slotSeekForwardShort ); action = new QAction( QIcon::fromTheme( "media-seek-forward-amarok" ), i18n( "Seek Forward by %1 seconds", KFormat().formatDecimalDuration( AmarokConfig::seekMedium() * 1000 ) ), this ); ac->addAction( "seek_forward_medium", action ); action->setShortcut( Qt::Key_Right ); KGlobalAccel::setGlobalShortcut(action, QKeySequence( Qt::META + Qt::SHIFT + Qt::Key_Plus ) ); connect( action, &QAction::triggered, this, &MainWindow::slotSeekForwardMedium ); action = new QAction( QIcon::fromTheme( "media-seek-forward-amarok" ), i18n( "Seek Forward by %1 seconds", KFormat().formatDecimalDuration( AmarokConfig::seekLong() * 1000 ) ), this ); ac->addAction( "seek_forward_long", action ); action->setShortcut( Qt::SHIFT + Qt::Key_Right ); connect( action, &QAction::triggered, this, &MainWindow::slotSeekForwardLong ); action = new QAction( QIcon::fromTheme( "media-seek-backward-amarok" ), i18n( "Seek Backward by %1 seconds", KFormat().formatDecimalDuration( AmarokConfig::seekShort() * 1000 ) ), this ); ac->addAction( "seek_backward_short", action ); action->setShortcut( Qt::CTRL + Qt::Key_Left ); connect( action, &QAction::triggered, this, &MainWindow::slotSeekBackwardShort ); action = new QAction( QIcon::fromTheme( "media-seek-backward-amarok" ), i18n( "Seek Backward by %1 seconds", KFormat().formatDecimalDuration( AmarokConfig::seekMedium() * 1000 ) ), this ); ac->addAction( "seek_backward_medium", action ); action->setShortcut( Qt::Key_Left ); KGlobalAccel::setGlobalShortcut(action, QKeySequence( Qt::META + Qt::SHIFT + Qt::Key_Minus ) ); connect( action, &QAction::triggered, this, &MainWindow::slotSeekBackwardMedium ); action = new QAction( QIcon::fromTheme( "media-seek-backward-amarok" ), i18n( "Seek Backward by %1 seconds", KFormat().formatDecimalDuration( AmarokConfig::seekLong() * 1000 ) ), this ); ac->addAction( "seek_backward_long", action ); action->setShortcut( Qt::SHIFT + Qt::Key_Left ); connect( action, &QAction::triggered, this, &MainWindow::slotSeekBackwardLong ); PERF_LOG( "MainWindow::createActions 6" ) action = new QAction( QIcon::fromTheme("view-refresh"), i18n( "Update Collection" ), this ); connect ( action, &QAction::triggered, CollectionManager::instance(), &CollectionManager::checkCollectionChanges ); ac->addAction( "update_collection", action ); action = new QAction( QIcon::fromTheme( "amarok_playcount" ), i18n( "Synchronize Statistics..." ), this ); ac->addAction( "synchronize_statistics", action ); connect( action, &QAction::triggered, Amarok::Components::statSyncingController(), &StatSyncing::Controller::synchronize ); Amarok::Components::statSyncingController(); action = new QAction( this ); ac->addAction( "prev", action ); action->setIcon( QIcon::fromTheme("media-skip-backward-amarok") ); action->setText( i18n( "Previous Track" ) ); KGlobalAccel::setGlobalShortcut(action, QKeySequence() ); connect( action, &QAction::triggered, pa, &Playlist::Actions::back ); action = new QAction( this ); ac->addAction( "replay", action ); action->setIcon( QIcon::fromTheme("media-playback-start") ); action->setText( i18n( "Restart current track" ) ); KGlobalAccel::setGlobalShortcut(action, QKeySequence() ); connect( action, &QAction::triggered, ec, &EngineController::replay ); action = new QAction( this ); ac->addAction( "shuffle_playlist", action ); action->setIcon( QIcon::fromTheme("media-playlist-shuffle") ); action->setText( i18n( "Shuffle Playlist" ) ); action->setShortcut( Qt::CTRL + Qt::Key_H ); connect( action, &QAction::triggered, this, &MainWindow::slotShufflePlaylist ); action = new QAction( this ); ac->addAction( "repopulate", action ); action->setText( i18n( "Repopulate Playlist" ) ); action->setIcon( QIcon::fromTheme("view-refresh-amarok") ); connect( action, &QAction::triggered, pa, &Playlist::Actions::repopulateDynamicPlaylist ); action = new QAction( this ); ac->addAction( "disable_dynamic", action ); action->setText( i18n( "Disable Dynamic Playlist" ) ); action->setIcon( QIcon::fromTheme("edit-delete-amarok") ); //this is connected inside the dynamic playlist category action = new QAction( QIcon::fromTheme("media-skip-forward-amarok"), i18n( "Next Track" ), this ); ac->addAction( "next", action ); KGlobalAccel::setGlobalShortcut(action, QKeySequence() ); connect( action, &QAction::triggered, pa, &Playlist::Actions::next ); action = new QAction( i18n( "Increase Volume" ), this ); ac->addAction( "increaseVolume", action ); KGlobalAccel::setGlobalShortcut(action, QKeySequence( Qt::META + Qt::Key_Plus ) ); action->setShortcut( Qt::Key_Plus ); connect( action, &QAction::triggered, ec, &EngineController::increaseVolume ); action = new QAction( i18n( "Decrease Volume" ), this ); ac->addAction( "decreaseVolume", action ); KGlobalAccel::setGlobalShortcut(action, QKeySequence( Qt::META + Qt::Key_Minus ) ); action->setShortcut( Qt::Key_Minus ); connect( action, &QAction::triggered, ec, &EngineController::decreaseVolume ); action = new QAction( i18n( "Toggle Main Window" ), this ); ac->addAction( "toggleMainWindow", action ); KGlobalAccel::setGlobalShortcut(action, QKeySequence() ); connect( action, &QAction::triggered, this, &MainWindow::showHide ); action = new QAction( i18n( "Toggle Full Screen" ), this ); ac->addAction( "toggleFullScreen", action ); action->setShortcut( QKeySequence( Qt::CTRL + Qt::SHIFT + Qt::Key_F ) ); connect( action, &QAction::triggered, this, &MainWindow::slotFullScreen ); action = new QAction( i18n( "Search playlist" ), this ); ac->addAction( "searchPlaylist", action ); action->setShortcut( QKeySequence( Qt::CTRL + Qt::Key_J ) ); connect( action, &QAction::triggered, this, &MainWindow::slotFocusPlaylistSearch ); action = new QAction( i18n( "Search collection" ), this ); ac->addAction( "searchCollection", action ); action->setShortcut( QKeySequence( Qt::CTRL + Qt::Key_F ) ); connect( action, &QAction::triggered, this, &MainWindow::slotFocusCollectionSearch ); action = new QAction( QIcon::fromTheme( "music-amarok" ), i18n("Show active track"), this ); ac->addAction( "show_active_track", action ); connect( action, &QAction::triggered, this, &MainWindow::slotShowActiveTrack ); action = new QAction( i18n( "Show Notification Popup" ), this ); ac->addAction( "showNotificationPopup", action ); KGlobalAccel::setGlobalShortcut(action, QKeySequence( Qt::META + Qt::Key_O ) ); connect( action, &QAction::triggered, this, &MainWindow::showNotificationPopup ); action = new QAction( i18n( "Mute Volume" ), this ); ac->addAction( "mute", action ); KGlobalAccel::setGlobalShortcut(action, QKeySequence( Qt::META + Qt::Key_M ) ); connect( action, &QAction::triggered, ec, &EngineController::toggleMute ); action = new QAction( i18n( "Last.fm: Love Current Track" ), this ); ac->addAction( "loveTrack", action ); KGlobalAccel::setGlobalShortcut(action, QKeySequence( Qt::META + Qt::Key_L ) ); connect( action, &QAction::triggered, this, &MainWindow::slotLoveTrack ); action = new QAction( i18n( "Last.fm: Ban Current Track" ), this ); ac->addAction( "banTrack", action ); //KGlobalAccel::setGlobalShortcut(action, QKeySequence( Qt::META + Qt::Key_B ) ); connect( action, &QAction::triggered, this, &MainWindow::slotBanTrack ); action = new QAction( i18n( "Last.fm: Skip Current Track" ), this ); ac->addAction( "skipTrack", action ); KGlobalAccel::setGlobalShortcut(action, QKeySequence( Qt::META + Qt::Key_S ) ); connect( action, &QAction::triggered, this, &MainWindow::skipTrack ); action = new QAction( QIcon::fromTheme( "media-track-queue-amarok" ), i18n( "Queue Track" ), this ); ac->addAction( "queueTrack", action ); action->setShortcut( QKeySequence( Qt::CTRL + Qt::Key_D ) ); connect( action, &QAction::triggered, this, &MainWindow::switchQueueStateShortcut ); action = new QAction( i18n( "Put Artist - Title of the current track to the clipboard" ), this ); ac->addAction("artistTitleClipboard", action); action->setShortcut( QKeySequence( Qt::CTRL + Qt::Key_C ) ); connect( action, &QAction::triggered, this, &MainWindow::slotPutCurrentTrackToClipboard ); action = new QAction( i18n( "Rate Current Track: 1" ), this ); ac->addAction( "rate1", action ); KGlobalAccel::setGlobalShortcut(action, QKeySequence( Qt::META + Qt::Key_1 ) ); connect( action, &QAction::triggered, this, &MainWindow::setRating1 ); action = new QAction( i18n( "Rate Current Track: 2" ), this ); ac->addAction( "rate2", action ); KGlobalAccel::setGlobalShortcut(action, QKeySequence( Qt::META + Qt::Key_2 ) ); connect( action, &QAction::triggered, this, &MainWindow::setRating2 ); action = new QAction( i18n( "Rate Current Track: 3" ), this ); ac->addAction( "rate3", action ); KGlobalAccel::setGlobalShortcut(action, QKeySequence( Qt::META + Qt::Key_3 ) ); connect( action, &QAction::triggered, this, &MainWindow::setRating3 ); action = new QAction( i18n( "Rate Current Track: 4" ), this ); ac->addAction( "rate4", action ); KGlobalAccel::setGlobalShortcut(action, QKeySequence( Qt::META + Qt::Key_4 ) ); connect( action, &QAction::triggered, this, &MainWindow::setRating4 ); action = new QAction( i18n( "Rate Current Track: 5" ), this ); ac->addAction( "rate5", action ); KGlobalAccel::setGlobalShortcut(action, QKeySequence( Qt::META + Qt::Key_5 ) ); connect( action, &QAction::triggered, this, &MainWindow::setRating5 ); #ifdef DEBUG_BUILD_TYPE action = new QAction( i18n( "Network Request Viewer" ), this ); ac->addAction( "network_request_viewer", action ); action->setIcon( QIcon::fromTheme( "utilities-system-monitor" ) ); connect( action, &QAction::triggered, this, &MainWindow::showNetworkRequestViewer ); #endif // DEBUG_BUILD_TYPE action = KStandardAction::redo( pc, &Playlist::Controller::redo, this); ac->addAction( "playlist_redo", action ); action->setEnabled( false ); action->setIcon( QIcon::fromTheme( "edit-redo" ) ); connect( pc, &Playlist::Controller::canRedoChanged, action, &QAction::setEnabled ); action = KStandardAction::undo( pc, &Playlist::Controller::undo, this); ac->addAction( "playlist_undo", action ); action->setEnabled( false ); action->setIcon( QIcon::fromTheme( "edit-undo" ) ); connect( pc, &Playlist::Controller::canUndoChanged, action, &QAction::setEnabled ); action = new QAction( QIcon::fromTheme( "amarok" ), i18n( "&About Amarok" ), this ); ac->addAction( "extendedAbout", action ); connect( action, &QAction::triggered, this, &MainWindow::showAbout ); action = new QAction ( QIcon::fromTheme( "info-amarok" ), i18n( "&Diagnostics" ), this ); ac->addAction( "diagnosticDialog", action ); connect( action, &QAction::triggered, this, &MainWindow::slotShowDiagnosticsDialog ); action = new QAction( QIcon::fromTheme( "tools-report-bug" ), i18n("&Report Bug..."), this ); ac->addAction( "reportBug", action ); connect( action, &QAction::triggered, this, &MainWindow::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 QMenu *actionsMenu = new QMenu( m_menubar ); #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 QMenu *playlistMenu = new QMenu( 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 QMenu( m_menubar.data() ); m_toolsMenu->setTitle( i18n("&Tools") ); m_toolsMenu->addAction( Amarok::actionCollection()->action("bookmark_manager") ); m_toolsMenu->addAction( Amarok::actionCollection()->action("cover_manager") ); m_toolsMenu->addAction( Amarok::actionCollection()->action("equalizer_dialog") ); #ifdef DEBUG_BUILD_TYPE m_toolsMenu->addAction( Amarok::actionCollection()->action("network_request_viewer") ); #endif // DEBUG_BUILD_TYPE m_toolsMenu->addSeparator(); m_toolsMenu->addAction( Amarok::actionCollection()->action("update_collection") ); m_toolsMenu->addAction( Amarok::actionCollection()->action("synchronize_statistics") ); //END Tools menu //BEGIN Settings menu m_settingsMenu = new QMenu( m_menubar.data() ); m_settingsMenu->setTitle( i18n("&Settings") ); m_settingsMenu->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->addAction( Amarok::actionCollection()->action("replay_gain_mode") ); m_settingsMenu->addSeparator(); #endif m_settingsMenu->addAction( Amarok::actionCollection()->action( KStandardAction::name( KStandardAction::KeyBindings ) ) ); m_settingsMenu->addAction( Amarok::actionCollection()->action( KStandardAction::name( KStandardAction::Preferences ) ) ); //END Settings menu m_menubar->addMenu( actionsMenu ); m_menubar->addMenu( viewMenu ); m_menubar->addMenu( playlistMenu ); m_menubar->addMenu( m_toolsMenu.data() ); m_menubar->addMenu( m_settingsMenu.data() ); QMenu *helpMenu = Amarok::Menu::helpMenu(); helpMenu->insertAction( helpMenu->actions().last(), Amarok::actionCollection()->action( "extendedAbout" ) ); helpMenu->insertAction( helpMenu->actions().last(), Amarok::actionCollection()->action( "diagnosticDialog" ) ); m_menubar->addSeparator(); m_menubar->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( KAboutData::applicationData(), &ocsData ); dialog.exec(); } void MainWindow::showReportBug() { KBugReport * rbDialog = new KBugReport( KAboutData::applicationData() ,this ); 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->list()->activeCategory() ) return m_browserDock->list()->activeCategory()->name(); else return QString(); } void MainWindow::setLayoutLocked( bool locked ) { DEBUG_BLOCK if( m_browserDock ) m_browserDock.data()->setMovable( !locked ); if( m_contextDock ) m_contextDock.data()->setMovable( !locked ); if( m_playlistDock ) m_playlistDock.data()->setMovable( !locked ); if( m_slimToolbar ) { m_slimToolbar.data()->setFloatable( !locked ); m_slimToolbar.data()->setMovable( !locked ); } if( m_mainToolbar ) { m_mainToolbar.data()->setFloatable( !locked ); m_mainToolbar.data()->setMovable( !locked ); } AmarokConfig::setLockLayout( locked ); AmarokConfig::self()->save(); } 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->setFloating( false ); m_contextDock->setFloating( false ); m_playlistDock->setFloating( false ); m_browserDock->show(); m_contextDock->show(); m_playlistDock->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->minimumWidth(), m_contextDock->minimumWidth(), m_playlistDock->minimumWidth() }; const int maxs[3] = { m_browserDock->maximumWidth(), m_contextDock->maximumWidth(), m_playlistDock->maximumWidth() }; m_browserDock->setFixedWidth( widgetWidth * 0.65 ); m_contextDock->setFixedWidth( widgetWidth * 1.7 + leftover ); m_playlistDock->setFixedWidth( widgetWidth * 0.65 ); // Important: We need to activate the layout we have just set layout()->activate(); m_browserDock->setMinimumWidth( mins[0] ); m_browserDock->setMaximumWidth( maxs[0] ); m_contextDock->setMinimumWidth( mins[1] ); m_contextDock->setMaximumWidth( maxs[1] ); m_playlistDock->setMinimumWidth( mins[2] ); m_playlistDock->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/MainWindow.h b/src/MainWindow.h index db1306f383..d200eba33f 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -1,221 +1,217 @@ /**************************************************************************************** * Copyright (c) 2002-2013 Mark Kretschmann * * Copyright (c) 2002 Max Howell * * * * This program is free software; you can redistribute it and/or modify it under * * the 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 MAINWINDOW_H #define MAINWINDOW_H #include #include "amarok_export.h" #include "core/meta/forward_declarations.h" #include #include #include #include class CollectionWidget; class SlimToolbar; class MainToolbar; class MainWindow; #ifdef DEBUG_BUILD_TYPE class NetworkAccessViewer; #endif // DEBUG_BUILD_TYPE class PlaylistFileProvider; namespace PlaylistBrowserNS { class PlaylistBrowser; } namespace Playlist { class Dock; } class BrowserDock; class ContextDock; class QMenu; class QAction; class QMenuBar; class QSplitter; class QTimer; namespace The { AMAROK_EXPORT MainWindow* mainWindow(); } //This should only change if docks or toolbars are added or removed #define LAYOUT_VERSION 3 /** * @class MainWindow * @short The MainWindow widget class. * * This is the main window widget. */ class AMAROK_EXPORT MainWindow : public KMainWindow { - friend MainWindow* The::mainWindow(); - Q_OBJECT public: enum AmarokDockId { AmarokDockNavigation, AmarokDockContext, AmarokDockPlaylist }; MainWindow(); ~MainWindow(); void activate(); //allows us to switch browsers from within other browsers etc void showBrowser( const QString& name ); //ensures the dock widget is visible in case it is tabbed void showDock( AmarokDockId dockId ); QPointer browserDock() const { return m_browserDock; } QPointer ToolsMenu() const { return m_toolsMenu; } QPointer SettingsMenu() const { return m_settingsMenu; } QPointer playlistDock() const { return m_playlistDock; } void deleteBrowsers(); /* Reimplemented from QMainWindow to allow only one active toolbar at any time */ virtual QMenu* createPopupMenu(); void addViewMenuItems(QMenu* menu); QString activeBrowserName(); CollectionWidget * collectionBrowser(); bool isLayoutLocked() const; /** * If an audiocd collection is present. Stop current playback, clear playlist, * add cd to playlist and start playback */ bool playAudioCd(); bool isWaitingForCd() const; /** * @return Whether the application is on the currently active virtual desktop. On non-X11 systems this is always true. */ bool isOnCurrentDesktop() const; Q_SIGNALS: void loveTrack( Meta::TrackPtr track ); void banTrack( Meta::TrackPtr track ); void skipTrack(); void switchQueueStateShortcut(); public Q_SLOTS: void showHide(); void slotFullScreen(); void showNotificationPopup(); void setLayoutLocked( bool locked ); void resetLayout(); void showAbout(); void showReportBug(); private Q_SLOTS: void setDefaultDockSizes(); void slotLoveTrack(); void slotBanTrack(); void slotStopped(); void slotPaused(); void slotNewTrackPlaying(); void slotMetadataChanged( Meta::TrackPtr track ); void exportPlaylist(); void slotShowActiveTrack() const; void slotEditTrackInfo() const; void slotShowBookmarkManager(); void slotShowEqualizer(); void slotShowCoverManager(); void slotShowDiagnosticsDialog(); void slotShowMenuBar(); void slotPlayMedia(); void slotAddLocation( bool directPlay = false ); void slotAddStream(); void slotFocusPlaylistSearch(); void slotFocusCollectionSearch(); void slotShufflePlaylist(); void slotSeekForwardShort(); void slotSeekForwardMedium(); void slotSeekForwardLong(); void slotSeekBackwardShort(); void slotSeekBackwardMedium(); void slotSeekBackwardLong(); void slotPutCurrentTrackToClipboard(); #ifdef DEBUG_BUILD_TYPE void showNetworkRequestViewer(); #endif // DEBUG_BUILD_TYPE protected: virtual void closeEvent( QCloseEvent* ); virtual void changeEvent( QEvent *event ); private Q_SLOTS: void setRating1() { setRating( 1 ); } void setRating2() { setRating( 2 ); } void setRating3() { setRating( 3 ); } void setRating4() { setRating( 4 ); } void setRating5() { setRating( 5 ); } private: void init(); void setRating( int n ); CollectionWidget * m_collectionBrowser; PlaylistBrowserNS::PlaylistBrowser * m_playlistBrowser; QPointer m_menubar; QPointer m_toolsMenu; QPointer m_settingsMenu; #ifdef DEBUG_BUILD_TYPE QPointer m_networkViewer; #endif // DEBUG_BUILD_TYPE QPointer m_browserDock; QPointer m_contextDock; QPointer m_playlistDock; QPointer m_slimToolbar; QPointer m_mainToolbar; void createActions(); void createMenus(); KToggleAction* m_showMenuBar; int m_lastBrowser; int m_searchField; - static QPointer s_instance; - bool m_waitingForCd; }; #endif //AMAROK_PLAYLISTWINDOW_H diff --git a/src/PluginManager.cpp b/src/PluginManager.cpp index 8a1a431712..ad9d24df3c 100644 --- a/src/PluginManager.cpp +++ b/src/PluginManager.cpp @@ -1,321 +1,322 @@ /**************************************************************************************** * Copyright (c) 2004-2013 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 . * ****************************************************************************************/ #define DEBUG_PREFIX "PluginManager" #include "PluginManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** Defines the used plugin version number. * * This must match the desktop files. */ const int Plugins::PluginManager::s_pluginFrameworkVersion = 73; Plugins::PluginManager* Plugins::PluginManager::s_instance = Q_NULLPTR; Plugins::PluginManager* Plugins::PluginManager::instance() { return s_instance ? s_instance : new PluginManager(); } void Plugins::PluginManager::destroy() { if( s_instance ) { delete s_instance; s_instance = Q_NULLPTR; } } Plugins::PluginManager::PluginManager( QObject *parent ) : QObject( parent ) { DEBUG_BLOCK setObjectName( "PluginManager" ); s_instance = this; PERF_LOG( "Initialising Plugin Manager" ) init(); PERF_LOG( "Initialised Plugin Manager" ) } Plugins::PluginManager::~PluginManager() { // tell the managers to get rid of their current factories - QList emptyFactories; + QList > emptyFactories; StatSyncing::Controller *controller = Amarok::Components::statSyncingController(); if( controller ) controller->setFactories( emptyFactories ); ServicePluginManager::instance()->setFactories( emptyFactories ); CollectionManager::instance()->setFactories( emptyFactories ); StorageManager::instance()->setFactories( emptyFactories ); } void Plugins::PluginManager::init() { checkPluginEnabledStates(); } KPluginInfo::List Plugins::PluginManager::plugins( Type type ) const { KPluginInfo::List infos; for( const auto &pluginInfo : m_pluginsByType.value( type ) ) { auto info = KPluginInfo( pluginInfo ); info.setConfig( Amarok::config( "Plugins" ) ); infos << info; } return infos; } QList Plugins::PluginManager::enabledPlugins(Plugins::PluginManager::Type type) const { QList enabledList; for( const auto &plugin : m_pluginsByType.value( type ) ) { if( isPluginEnabled( plugin ) ) enabledList << plugin; } return enabledList; } -QList +QList > Plugins::PluginManager::factories( Type type ) const { return m_factoriesByType.value( type ); } void Plugins::PluginManager::checkPluginEnabledStates() { DEBUG_BLOCK // re-create all the member infos. m_plugins.clear(); m_pluginsByType.clear(); m_factoriesByType.clear(); m_plugins = findPlugins(); // reload all the plugins plus their enabled state if( m_plugins.isEmpty() ) // exit if no plugins are found { if( qobject_cast( qApp ) ) { KMessageBox::error( 0, i18n( "Amarok could not find any plugins. This indicates an installation problem." ) ); } else { warning() << "Amarok could not find any plugins. Bailing out."; } // don't use QApplication::exit, as the eventloop may not have started yet std::exit( EXIT_SUCCESS ); } // sort the plugin infos by type for( const auto &pluginInfo : m_plugins ) { // create the factories and sort them by type - PluginFactory *factory = createFactory( pluginInfo ); + auto factory = createFactory( pluginInfo ); if( factory ) { Type type; if( qobject_cast( factory ) ) type = Storage; else if( qobject_cast( factory ) ) type = Collection; else if( qobject_cast( factory ) ) type = Service; else if( qobject_cast( factory ) ) type = Importer; else { warning() << pluginInfo.name() << "has unknown category"; warning() << pluginInfo.rawData().keys(); continue; } m_pluginsByType[ type ] << pluginInfo; if( isPluginEnabled( pluginInfo ) ) m_factoriesByType[ type ] << factory; } else warning() << pluginInfo.name() << "could not create factory"; } // the setFactories functions should: // - filter out factories not useful (e.g. services when setting collections) // - handle the new list of factories, disabling old ones and enabling new ones. PERF_LOG( "Loading storage plugins" ) StorageManager::instance()->setFactories( m_factoriesByType.value( Storage ) ); PERF_LOG( "Loaded storage plugins" ) PERF_LOG( "Loading collection plugins" ) CollectionManager::instance()->setFactories( m_factoriesByType.value( Collection ) ); PERF_LOG( "Loaded collection plugins" ) PERF_LOG( "Loading service plugins" ) ServicePluginManager::instance()->setFactories( m_factoriesByType.value( Service ) ); PERF_LOG( "Loaded service plugins" ) PERF_LOG( "Loading importer plugins" ) StatSyncing::Controller *controller = Amarok::Components::statSyncingController(); if( controller ) controller->setFactories( m_factoriesByType.value( Importer ) ); PERF_LOG( "Loaded importer plugins" ) // init all new factories // do this after they were added to the sub-manager so that they // have a chance to connect to signals // // we need to init by type and the storages need to go first for( const auto &factory : m_factoriesByType[ Storage ] ) factory->init(); for( const auto &factory : m_factoriesByType[ Collection ] ) factory->init(); for( const auto &factory : m_factoriesByType[ Service ] ) factory->init(); for( const auto &factory : m_factoriesByType[ Importer ] ) factory->init(); } bool Plugins::PluginManager::isPluginEnabled( const KPluginMetaData &plugin ) const { // mysql storage and collection are vital. They need to be loaded always auto raw = plugin.rawData(); int version = raw.value( "X-KDE-Amarok-framework-version" ).toInt(); int rank = raw.value( "X-KDE-Amarok-rank" ).toInt(); if( version != s_pluginFrameworkVersion ) { warning() << "Plugin" << plugin.pluginId() << "has frameworks version" << version << ". Version" << s_pluginFrameworkVersion << "is required"; return false; } if( rank == 0 ) { warning() << "Plugin" << plugin.pluginId() << "has rank 0"; return false; } auto vital = raw.value( QStringLiteral( "X-KDE-Amarok-vital" ) ); if( !vital.isUndefined()) { if( vital.toBool() || vital.toString().toLower() == "true" ) { debug() << "Plugin" << plugin.pluginId() << "is vital"; return true; } } KPluginInfo info = KPluginInfo( plugin ); info.setConfig( Amarok::config( "Plugins" ) ); info.load(); return info.isPluginEnabled(); } -Plugins::PluginFactory* +QSharedPointer Plugins::PluginManager::createFactory( const KPluginMetaData &pluginInfo ) { debug() << "Creating factory for plugin:" << pluginInfo.pluginId(); // check if we already created this factory // note: old factories are not deleted. // We can't very well just destroy a factory being // currently used. const QString name = pluginInfo.pluginId(); if( m_factoryCreated.contains( name ) ) return m_factoryCreated.value( name ); QPluginLoader loader( pluginInfo.fileName() ); - auto pluginFactory = qobject_cast( loader.instance() ); + auto pointer = qobject_cast( loader.instance() ); + auto pluginFactory = QSharedPointer( pointer ); if( !pluginFactory ) { warning() << QString( "Failed to get factory '%1' from QPluginLoader: %2" ) .arg( name, loader.errorString() ); - return Q_NULLPTR; + return QSharedPointer(); } m_factoryCreated[ name ] = pluginFactory; return pluginFactory; } QVector Plugins::PluginManager::findPlugins() { QVector plugins; for( const auto &location : QCoreApplication::libraryPaths() ) plugins << KPluginLoader::findPlugins( location, [] ( const KPluginMetaData &metadata ) { return metadata.serviceTypes().contains( QStringLiteral( "Amarok/Plugin" ) ); } ); for( const auto &plugin : plugins ) { bool enabled = isPluginEnabled( plugin ); debug() << "found plugin:" << plugin.pluginId() << "enabled:" << enabled; } debug() << plugins.count() << "plugins in total"; return plugins; } int Plugins::PluginManager::pluginFrameworkVersion() { return s_pluginFrameworkVersion; } diff --git a/src/PluginManager.h b/src/PluginManager.h index e45cf4b203..4e994424d6 100644 --- a/src/PluginManager.h +++ b/src/PluginManager.h @@ -1,123 +1,120 @@ /**************************************************************************************** * Copyright (c) 2004-2013 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_PLUGINMANAGER_H #define AMAROK_PLUGINMANAGER_H #include "amarok_export.h" #include #include namespace Plugins { class PluginFactory; class AMAROK_EXPORT PluginManager : public QObject { Q_OBJECT Q_PROPERTY( int pluginFrameworkVersion READ pluginFrameworkVersion ) public: /** Type of the plugin. * * Will be determined by the KPluginInfo::category */ enum Type { Collection = 1, ///< the plugin implements a CollectionFactory Service = 2, ///< this is a service plugin Importer = 3, ///< this plugin implements importer functionality Storage = 4, ///< the plugin implements a StorageFactory }; Q_ENUM( Type ) ~PluginManager(); static PluginManager *instance(); /** Destroys the instance of the PluginManager. * * The order of the destruction is somewhat important. * The PluginManager needs to be destroyed after all collections * have been removed and before the CollectionManager, * the ServicePluginManager and the StatSyncing::Controller are destroyed. */ static void destroy(); static int pluginFrameworkVersion(); /** * Load any services that are configured to be loaded */ void init(); /** Returns enabled plugin factories for the given plugin type. * - * This function will only return enable factories. - * - * Owner of the PluginFactory pointers is the PluginManager - * and the pointers will only be valid while the PluginManager exists. + * This function will only return factories for enabled plugins. */ - QList factories( Type type ) const; + QList > factories( Type type ) const; KPluginInfo::List plugins( Type type ) const; QList enabledPlugins (Type type ) const; /** Check if any services were disabled and needs to be removed, or any * that are hidden needs to be enabled * * This function will call the sub plugin managers (like CollectionManager) * setFactories function. */ void checkPluginEnabledStates(); private: /** Tries finding Amarok plugins */ QVector findPlugins(); /** Returns true if the plugin is enabled. * This function will check the default enabled state, * the Amarok configuration state and the primary collection. * * @returns true if the plugin is enabled. */ bool isPluginEnabled( const KPluginMetaData &plugin ) const; - /** Creates a factories for an info */ - PluginFactory* createFactory( const KPluginMetaData &pluginInfo ); + /** Creates a factories for a plugin */ + QSharedPointer createFactory( const KPluginMetaData &pluginInfo ); /// contains the names of all KPluginInfos that have factories created QVector m_plugins; QHash > m_pluginsByType; - QHash > m_factoriesByType; - QHash m_factoryCreated; + QHash > > m_factoriesByType; + QHash> m_factoryCreated; static const int s_pluginFrameworkVersion; static PluginManager *s_instance; explicit PluginManager( QObject *parent = 0 ); }; } // namespace Plugins namespace The { inline Plugins::PluginManager *pluginManager() { return Plugins::PluginManager::instance(); } } #endif /* AMAROK_PLUGINMANAGER_H */ diff --git a/src/TrayIcon.cpp b/src/TrayIcon.cpp index ae3c4e757b..9701ac5718 100644 --- a/src/TrayIcon.cpp +++ b/src/TrayIcon.cpp @@ -1,337 +1,345 @@ /**************************************************************************************** * Copyright (c) 2003 Stanislav Karchebny * * Copyright (c) 2003 Max Howell * * Copyright (c) 2004 Enrico Ros * * Copyright (c) 2006 Ian Monroe * * Copyright (c) 2009-2011 Kevin Funk * * 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 "TrayIcon.h" #include "App.h" #include "EngineController.h" #include "GlobalCurrentTrackActions.h" #include "SvgHandler.h" #include "amarokconfig.h" #include "core/capabilities/ActionsCapability.h" #include "core/capabilities/BookmarkThisCapability.h" #include "core/meta/Meta.h" #include "core/meta/Statistics.h" #include "core/support/Amarok.h" #include "playlist/PlaylistActions.h" #include #include #include #include #include #include #include #include #ifdef Q_WS_MAC extern void qt_mac_set_dock_menu(QMenu *); #endif Amarok::TrayIcon::TrayIcon( QObject *parent ) : KStatusNotifierItem( parent ) , m_track( The::engineController()->currentTrack() ) { PERF_LOG( "Beginning TrayIcon Constructor" ); KActionCollection* const ac = Amarok::actionCollection(); setStatus( KStatusNotifierItem::Active ); // Remove the "Configure Amarok..." action, as it makes no sense in the tray menu const QString preferences = KStandardAction::name( KStandardAction::Preferences ); contextMenu()->removeAction( ac->action( preferences ) ); PERF_LOG( "Before adding actions" ); #ifdef Q_WS_MAC // Add these functions to the dock icon menu in OS X qt_mac_set_dock_menu( contextMenu() ); contextMenu()->addAction( ac->action( "playlist_playmedia" ) ); contextMenu()->addSeparator(); #endif contextMenu()->addAction( ac->action( "prev" ) ); contextMenu()->addAction( ac->action( "play_pause" ) ); contextMenu()->addAction( ac->action( "stop" ) ); contextMenu()->addAction( ac->action( "next" ) ); contextMenu()->addSeparator(); contextMenu()->setObjectName( "TrayIconContextMenu" ); PERF_LOG( "Initializing system tray icon" ); setIconByName( "amarok" ); updateOverlayIcon(); updateToolTipIcon(); updateMenu(); const EngineController* engine = The::engineController(); connect( engine, &EngineController::trackPlaying, this, &TrayIcon::trackPlaying ); connect( engine, &EngineController::stopped, this, &TrayIcon::stopped ); connect( engine, &EngineController::paused, this, &TrayIcon::paused ); connect( engine, &EngineController::trackMetadataChanged, this, &TrayIcon::trackMetadataChanged ); connect( engine, &EngineController::albumMetadataChanged, this, &TrayIcon::albumMetadataChanged ); connect( engine, &EngineController::volumeChanged, this, &TrayIcon::updateToolTip ); connect( engine, &EngineController::muteStateChanged, this, &TrayIcon::updateToolTip ); connect( engine, &EngineController::playbackStateChanged, this, &TrayIcon::updateOverlayIcon ); connect( this, &TrayIcon::scrollRequested, this, &TrayIcon::slotScrollRequested ); connect( this, &TrayIcon::secondaryActivateRequested, The::engineController(), &EngineController::playPause ); } void Amarok::TrayIcon::updateToolTipIcon() { updateToolTip(); // the normal update if( m_track ) { if( m_track->album() && m_track->album()->hasImage() ) { QPixmap image = The::svgHandler()->imageWithBorder( m_track->album(), KIconLoader::SizeLarge, 5 ); setToolTipIconByPixmap( image ); } else { setToolTipIconByName( "amarok" ); } } else { setToolTipIconByName( "amarok" ); } } void Amarok::TrayIcon::updateToolTip() { if( m_track ) { setToolTipTitle( The::engineController()->prettyNowPlaying( false ) ); QStringList tooltip; QString volume; if ( The::engineController()->isMuted() ) { volume = i18n( "Muted" ); } else { volume = i18n( "%1%", The::engineController()->volume() ); } tooltip << i18n( "Volume: %1", volume ); Meta::StatisticsPtr statistics = m_track->statistics(); const float score = statistics->score(); if( score > 0.f ) { tooltip << i18n( "Score: %1", QString::number( score, 'f', 2 ) ); } const int rating = statistics->rating(); if( rating > 0 ) { QString stars; for( int i = 0; i < rating / 2; ++i ) stars += QString( "" ) .arg( QStandardPaths::locate( QStandardPaths::GenericDataLocation, "amarok/images/star.png" ) ) .arg( QFontMetrics( QToolTip::font() ).height() ) .arg( QFontMetrics( QToolTip::font() ).height() ); if( rating % 2 ) stars += QString( "" ) .arg( QStandardPaths::locate( QStandardPaths::GenericDataLocation, "amarok/images/smallstar.png" ) ) .arg( QFontMetrics( QToolTip::font() ).height() ) .arg( QFontMetrics( QToolTip::font() ).height() ); tooltip << i18n( "Rating: %1", stars ); } const int count = statistics->playCount(); if( count > 0 ) { tooltip << i18n( "Play count: %1", count ); } const QDateTime lastPlayed = statistics->lastPlayed(); tooltip << i18n( "Last played: %1", Amarok::verboseTimeSince( lastPlayed ) ); setToolTipSubTitle( tooltip.join("
") ); } else { setToolTipTitle( pApp->applicationDisplayName() ); setToolTipSubTitle( The::engineController()->prettyNowPlaying( false ) ); } } void Amarok::TrayIcon::trackPlaying( Meta::TrackPtr track ) { m_track = track; updateMenu(); updateToolTipIcon(); } void Amarok::TrayIcon::paused() { updateToolTipIcon(); } void Amarok::TrayIcon::stopped() { m_track = 0; updateMenu(); // remove custom track actions on stop updateToolTipIcon(); } void Amarok::TrayIcon::trackMetadataChanged( Meta::TrackPtr track ) { Q_UNUSED( track ) updateToolTip(); updateMenu(); } void Amarok::TrayIcon::albumMetadataChanged( Meta::AlbumPtr album ) { Q_UNUSED( album ) updateToolTipIcon(); updateMenu(); } void Amarok::TrayIcon::slotScrollRequested( int delta, Qt::Orientation orientation ) { Q_UNUSED( orientation ) The::engineController()->increaseVolume( delta / Amarok::VOLUME_SENSITIVITY ); } QAction* Amarok::TrayIcon::action( const QString& name, QMap actionByName ) { QAction* action = 0L; if ( !name.isEmpty() ) action = actionByName.value(name); return action; } void Amarok::TrayIcon::updateMenu() { foreach( QAction* action, m_extraActions ) { contextMenu()->removeAction( action ); // -- delete actions without parent (e.g. the ones from the capabilities) if( action && !action->parent() ) { delete action; } } QMap actionByName; foreach (QAction* action, actionCollection()) { actionByName.insert(action->text(), action); } m_extraActions.clear(); contextMenu()->removeAction( m_separator.data() ); delete m_separator.data(); if( m_track ) { foreach( QAction *action, The::globalCurrentTrackActions()->actions() ) + { m_extraActions.append( action ); + connect( action, &QObject::destroyed, this, [this, action]() { m_extraActions.removeAll( action ); } ); + } QScopedPointer ac( m_track->create() ); if( ac ) { QList actions = ac->actions(); foreach( QAction *action, actions ) + { m_extraActions.append( action ); + connect( action, &QObject::destroyed, this, [this, action]() { m_extraActions.removeAll( action ); } ); + } } QScopedPointer btc( m_track->create() ); if( btc ) { - m_extraActions.append( btc->bookmarkAction() ); + QAction *action = btc->bookmarkAction(); + m_extraActions.append( action ); + connect( action, &QObject::destroyed, this, [this, action]() { m_extraActions.removeAll( action ); } ); } } // second statement checks if the menu has already been populated (first startup), if not: do it if( m_extraActions.count() > 0 || contextMenu()->actions().last() != actionByName.value( "file_quit" ) ) { // remove the 2 bottom items, so we can push them to the bottom again contextMenu()->removeAction( action( "file_quit", actionByName ) ); contextMenu()->removeAction( action( "minimizeRestore", actionByName ) ); foreach( QAction* action, m_extraActions ) contextMenu()->addAction( action ); m_separator = contextMenu()->addSeparator(); // readd contextMenu()->addAction( action( "minimizeRestore", actionByName ) ); contextMenu()->addAction( action( "file_quit", actionByName ) ); } } void Amarok::TrayIcon::updateOverlayIcon() { if( The::engineController()->isPlaying() ) setOverlayIconByName( "media-playback-start" ); else if( The::engineController()->isPaused() ) setOverlayIconByName( "media-playback-pause" ); else setOverlayIconByName( QString() ); } diff --git a/src/TrayIcon.h b/src/TrayIcon.h index 86c4bf2b73..b472aca290 100644 --- a/src/TrayIcon.h +++ b/src/TrayIcon.h @@ -1,63 +1,63 @@ /**************************************************************************************** * Copyright (c) 2003 Stanislav Karchebny * * Copyright (c) 2009 Mark Kretschmann * * Copyright (c) 2009-2011 Kevin Funk * * * * This program is free software; you can redistribute it and/or modify it under * * the 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_TRAYICON_H #define AMAROK_TRAYICON_H #include // baseclass #include "core/meta/forward_declarations.h" -#include "core/support/SmartPointerList.h" #include + class QAction; namespace Amarok { class TrayIcon : public KStatusNotifierItem { Q_OBJECT public: explicit TrayIcon( QObject *parent ); private Q_SLOTS: void updateOverlayIcon(); void updateToolTipIcon(); void updateToolTip(); void updateMenu(); void trackPlaying( Meta::TrackPtr track ); void stopped(); void paused(); void trackMetadataChanged( Meta::TrackPtr track ); void albumMetadataChanged( Meta::AlbumPtr album ); void slotScrollRequested( int delta, Qt::Orientation orientation ); QAction* action( const QString& name, QMap actionByName ); private: Meta::TrackPtr m_track; - SmartPointerList m_extraActions; + QList m_extraActions; QPointer m_separator; }; } #endif // AMAROK_TRAYICON_H diff --git a/src/browsers/BrowserMessageArea.cpp b/src/browsers/BrowserMessageArea.cpp index 9c745bf6f2..97e75f3e79 100644 --- a/src/browsers/BrowserMessageArea.cpp +++ b/src/browsers/BrowserMessageArea.cpp @@ -1,166 +1,167 @@ /**************************************************************************************** * Copyright (c) 2011 Bart Cerneels . * ****************************************************************************************/ #include "BrowserMessageArea.h" #include "statusbar/LongMessageWidget.h" #define SHORT_MESSAGE_DURATION 5000 #define POPUP_MESSAGE_DURATION 5000 BrowserMessageArea::BrowserMessageArea( QWidget *parent ) : BoxWidget( true, parent ) , m_busy( false ) { setObjectName( "BrowserMessageArea" ); m_progressBar = new CompoundProgressBar( this ); connect( m_progressBar, &CompoundProgressBar::allDone, this, &BrowserMessageArea::hideProgress ); layout()->addWidget( m_progressBar ); m_progressBar->hide(); m_messageLabel = new QLabel( this ); m_messageLabel->setAlignment( Qt::AlignCenter ); m_messageLabel->setWordWrap( true ); m_messageLabel->hide(); m_shortMessageTimer = new QTimer( this ); m_shortMessageTimer->setSingleShot( true ); connect( m_shortMessageTimer, &QTimer::timeout, this, &BrowserMessageArea::nextShortMessage ); //register to carry MessageType across threads qRegisterMetaType( "MessageType" ); connect( this, &BrowserMessageArea::signalLongMessage, this, &BrowserMessageArea::slotLongMessage, Qt::QueuedConnection ); } void BrowserMessageArea::shortMessage( const QString &text ) { if( !m_busy ) { m_busy = true; m_messageLabel->setText( text ); m_messageLabel->show(); m_shortMessageTimer->start( SHORT_MESSAGE_DURATION ); } else { m_shortMessageQueue.append( text ); } } void BrowserMessageArea::longMessage( const QString &text, MessageType type ) { // The purpose of this emit is to make the operation thread safe. If this // method is called from a non-GUI thread, the "emit" relays it over the // event loop to the GUI thread, so that we can safely create widgets. emit signalLongMessage( text, type ); } void -BrowserMessageArea::newProgressOperation( KJob *job, const QString &text, QObject *obj, - const char *slot, Qt::ConnectionType type ) +BrowserMessageArea::newProgressOperationImpl( KJob *job, const QString &text, QObject *context, + const std::function &function, Qt::ConnectionType type ) { KJobProgressBar *newBar = new KJobProgressBar( 0, job ); newBar->setDescription( text ); connect( job, &KJob::destroyed, m_progressBar, &CompoundProgressBar::endProgressOperation ); - newBar->setAbortSlot( obj, slot, type ); + newBar->setAbortSlot( context, function, type ); m_progressBar->addProgressBar( newBar, job ); m_progressBar->show(); m_busy = true; } void -BrowserMessageArea::newProgressOperation( QNetworkReply *reply, const QString &text, QObject *obj, - const char *slot, Qt::ConnectionType type ) +BrowserMessageArea::newProgressOperationImpl( QNetworkReply *reply, const QString &text, QObject *obj, + const std::function &function, Qt::ConnectionType type ) { NetworkProgressBar *newBar = new NetworkProgressBar( 0, reply ); newBar->setDescription( text ); newBar->setAbortSlot( reply, &QNetworkReply::deleteLater ); connect( reply, &QNetworkReply::destroyed, m_progressBar, &CompoundProgressBar::endProgressOperation ); - newBar->setAbortSlot( obj, slot, type ); + newBar->setAbortSlot( obj, function, type ); m_progressBar->addProgressBar( newBar, reply ); m_progressBar->show(); m_busy = true; } void -BrowserMessageArea::newProgressOperation( QObject *sender, const QString &text, int maximum, - QObject *obj, const char *slot, Qt::ConnectionType type ) +BrowserMessageArea::newProgressOperationImpl( QObject *sender, const QMetaMethod &increment, const QMetaMethod &end, const QString &text, + int maximum, QObject *obj, const std::function &function, Qt::ConnectionType type ) { ProgressBar *newBar = new ProgressBar( 0 ); newBar->setDescription( text ); newBar->setMaximum( maximum ); connect( sender, &QObject::destroyed, m_progressBar, &CompoundProgressBar::endProgressOperation, Qt::QueuedConnection ); - connect( sender, SIGNAL(endProgressOperation(QObject*)), m_progressBar, - SLOT(endProgressOperation(QObject*)), Qt::QueuedConnection ); - connect( sender, SIGNAL(incrementProgress()), m_progressBar, - SLOT(slotIncrementProgress()), Qt::QueuedConnection ); - connect( sender, SIGNAL(totalSteps(int)), newBar, SLOT(slotTotalSteps(int)) ); - newBar->setAbortSlot( obj, slot, type ); + int endIndex = m_progressBar->staticMetaObject.indexOfMethod( SLOT(endProgressOperation(QObject*)) ); + auto endSlot = m_progressBar->staticMetaObject.method( endIndex ); + connect( sender, end, m_progressBar, + endSlot, Qt::QueuedConnection ); + int incrementIndex = m_progressBar->staticMetaObject.indexOfMethod( SLOT(slotIncrementProgress()) ); + auto incrementSlot = m_progressBar->staticMetaObject.method( incrementIndex ); + connect( sender, increment, m_progressBar, + incrementSlot, Qt::QueuedConnection ); + if( sender->staticMetaObject.indexOfSignal( SIGNAL(totalSteps(int)) ) != -1 ) + connect( sender, SIGNAL(totalSteps(int)), newBar, SLOT(slotTotalSteps(int)) ); + newBar->setAbortSlot( obj, function, type ); m_progressBar->addProgressBar( newBar, sender ); m_progressBar->show(); m_busy = true; } void BrowserMessageArea::hideProgress() //SLOT { m_progressBar->hide(); m_busy = false; nextShortMessage(); } void BrowserMessageArea::nextShortMessage() { if( m_shortMessageQueue.count() > 0 ) { m_busy = true; m_messageLabel->setText( m_shortMessageQueue.takeFirst() ); m_messageLabel->show(); m_shortMessageTimer->start( SHORT_MESSAGE_DURATION ); } else { m_messageLabel->hide(); m_busy = false; } } -void -BrowserMessageArea::hideLongMessage() -{ - sender()->deleteLater(); -} - void BrowserMessageArea::slotLongMessage( const QString &text, MessageType type ) { - LongMessageWidget *message = new LongMessageWidget( this, text, type ); - connect( message, &LongMessageWidget::closed, this, &BrowserMessageArea::hideLongMessage ); + Q_UNUSED(type) + + LongMessageWidget *message = new LongMessageWidget( text ); + connect( message, &LongMessageWidget::closed, message, &QObject::deleteLater ); } diff --git a/src/browsers/BrowserMessageArea.h b/src/browsers/BrowserMessageArea.h index 562a3d4c2c..94ff485bd1 100644 --- a/src/browsers/BrowserMessageArea.h +++ b/src/browsers/BrowserMessageArea.h @@ -1,70 +1,68 @@ /**************************************************************************************** * Copyright (c) 2011 Bart Cerneels . * ****************************************************************************************/ #ifndef BROWSERMESSAGEAREA_H #define BROWSERMESSAGEAREA_H #include "core-impl/logger/ProxyLogger.h" #include "statusbar/CompoundProgressBar.h" #include "statusbar/KJobProgressBar.h" #include "statusbar/NetworkProgressBar.h" #include "widgets/BoxWidget.h" #include class BrowserMessageArea : public BoxWidget, public Amarok::Logger { Q_OBJECT public: explicit BrowserMessageArea( QWidget *parent ); ~BrowserMessageArea() { } /* Amarok::Logger virtual methods */ virtual void shortMessage( const QString &text ); virtual void longMessage( const QString &text, MessageType type ); + virtual void newProgressOperationImpl( KJob * job, const QString & text, QObject *context, + const std::function &function, Qt::ConnectionType type ) override; - virtual void newProgressOperation( KJob *job, const QString &text, QObject *obj, - const char *slot, Qt::ConnectionType type ); + virtual void newProgressOperationImpl( QNetworkReply *reply, const QString &text, QObject *obj, + const std::function &function, Qt::ConnectionType type ) override; - virtual void newProgressOperation( QNetworkReply *reply, const QString &text, QObject *obj, - const char *slot, Qt::ConnectionType type ); - - virtual void newProgressOperation( QObject *sender, const QString &text, int maximum, - QObject *obj, const char *slot, Qt::ConnectionType type ); + virtual void newProgressOperationImpl( QObject *sender, const QMetaMethod &increment, const QMetaMethod &end, const QString &text, + int maximum, QObject *obj, const std::function &function, Qt::ConnectionType type ) override; Q_SIGNALS: void signalLongMessage( const QString & text, MessageType type ); private Q_SLOTS: void hideProgress(); void nextShortMessage(); - void hideLongMessage(); void slotLongMessage( const QString &text, MessageType type = Information ); private: CompoundProgressBar *m_progressBar; QLabel *m_messageLabel; bool m_busy; QTimer *m_shortMessageTimer; QList m_shortMessageQueue; }; #endif // BROWSERMESSAGEAREA_H diff --git a/src/configdialog/dialogs/DatabaseConfig.cpp b/src/configdialog/dialogs/DatabaseConfig.cpp index fe1a48f7d4..51b01db060 100644 --- a/src/configdialog/dialogs/DatabaseConfig.cpp +++ b/src/configdialog/dialogs/DatabaseConfig.cpp @@ -1,174 +1,172 @@ /**************************************************************************************** * Copyright (c) 2009 John Atkinson * * * * This program is free software; you can redistribute it and/or modify it under * * the 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 "DatabaseConfig.h" #include #include #include #include "core/support/PluginFactory.h" #include #include #include DatabaseConfig::DatabaseConfig( Amarok2ConfigDialog* parent, KConfigSkeleton *config ) : ConfigDialogBase( parent ) , m_configManager( new KConfigDialogManager( this, config ) ) { setupUi( this ); // Fix some weird tab orderness setTabOrder( kcfg_Host, kcfg_Port ); // host to port setTabOrder( kcfg_Port, kcfg_User ); // port to username setTabOrder( kcfg_User, kcfg_Password ); // username to password setTabOrder( kcfg_Password, kcfg_Database ); // password to database // enable the test button if one of the plugin factories has a correct testSettings slot // get all storage factories - QList factories; - factories = Plugins::PluginManager::instance()->factories( Plugins::PluginManager::Storage ); + auto factories = Plugins::PluginManager::instance()->factories( Plugins::PluginManager::Storage ); bool testFunctionAvailable = false; - foreach( Plugins::PluginFactory* factory, factories ) + for( const auto &factory : factories ) { // check the meta object if there is a testSettings slot available if( factory->metaObject()-> indexOfMethod( QMetaObject::normalizedSignature("testSettings(QString, QString, QString, int, QString)" ) ) >= 0 ) testFunctionAvailable = true; } button_Test->setEnabled( testFunctionAvailable ); // connect slots connect( kcfg_UseServer, &QCheckBox::stateChanged, this, &DatabaseConfig::toggleExternalConfigAvailable ); connect( kcfg_Database, &QLineEdit::textChanged, this, &DatabaseConfig::updateSQLQuery ); connect( kcfg_User, &QLineEdit::textChanged, this, &DatabaseConfig::updateSQLQuery ); connect( button_Test, &QAbstractButton::clicked, this, &DatabaseConfig::testDatabaseConnection ); toggleExternalConfigAvailable( kcfg_UseServer->checkState() ); updateSQLQuery(); m_configManager->addWidget( this ); } DatabaseConfig::~DatabaseConfig() {} void DatabaseConfig::toggleExternalConfigAvailable( const int checkBoxState ) //SLOT { group_Connection->setEnabled( checkBoxState == Qt::Checked ); } void DatabaseConfig::testDatabaseConnection() //SLOT { // get all storage factories - QList factories; - factories = Plugins::PluginManager::instance()->factories( Plugins::PluginManager::Storage ); + auto factories = Plugins::PluginManager::instance()->factories( Plugins::PluginManager::Storage ); // try if they have a testSettings slot that we can call - foreach( Plugins::PluginFactory* factory, factories ) + for( const auto &factory : factories ) { bool callSucceeded = false; QStringList connectionErrors; - callSucceeded = QMetaObject::invokeMethod( factory, + callSucceeded = QMetaObject::invokeMethod( factory.data(), "testSettings", Q_RETURN_ARG( QStringList, connectionErrors ), Q_ARG( QString, kcfg_Host->text() ), Q_ARG( QString, kcfg_User->text() ), Q_ARG( QString, kcfg_Password->text() ), Q_ARG( int, kcfg_Port->text().toInt() ), Q_ARG( QString, kcfg_Database->text() ) ); if( callSucceeded ) { if( connectionErrors.isEmpty() ) KMessageBox::messageBox( this, KMessageBox::Information, i18n( "Amarok was able to establish a successful connection to the database." ), i18n( "Success" ) ); else KMessageBox::error( this, 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( connectionErrors.join( "\n" ) ), i18n( "Database Error" )); } } } /////////////////////////////////////////////////////////////// // REIMPLEMENTED METHODS from ConfigDialogBase /////////////////////////////////////////////////////////////// bool DatabaseConfig::hasChanged() { return false; } bool DatabaseConfig::isDefault() { return false; } void DatabaseConfig::updateSettings() { if( m_configManager->hasChanged() ) KMessageBox::messageBox( 0, KMessageBox::Information, i18n( "Changes to database settings only take\neffect after Amarok is restarted." ), i18n( "Database settings changed" ) ); } /////////////////////////////////////////////////////////////// // PRIVATE METHODS /////////////////////////////////////////////////////////////// void DatabaseConfig::updateSQLQuery() //SLOT { QString query; if( isSQLInfoPresent() ) { // Query template: // GRANT ALL ON amarokdb.* TO 'amarokuser'@'localhost' IDENTIFIED BY 'mypassword'; FLUSH PRIVILEGES; // Don't print the actual password! const QString examplePassword = i18nc( "A default password for insertion into an example SQL command (so as not to print the real one). To be manually replaced by the user.", "password" ); query = QString( "CREATE DATABASE %1;\nGRANT ALL PRIVILEGES ON %1.* TO '%2' IDENTIFIED BY '%3'; FLUSH PRIVILEGES;" ) .arg( kcfg_Database->text(), kcfg_User->text(), examplePassword ); } text_SQL->setPlainText( query ); } inline bool DatabaseConfig::isSQLInfoPresent() const { return !kcfg_Database->text().isEmpty() && !kcfg_User->text().isEmpty() && !kcfg_Host->text().isEmpty(); } diff --git a/src/core-impl/collections/db/sql/SqlCollectionLocation.cpp b/src/core-impl/collections/db/sql/SqlCollectionLocation.cpp index bd97ba3198..582d65bf52 100644 --- a/src/core-impl/collections/db/sql/SqlCollectionLocation.cpp +++ b/src/core-impl/collections/db/sql/SqlCollectionLocation.cpp @@ -1,785 +1,785 @@ /**************************************************************************************** * 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( ) { //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; 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 &path ) { if( !QFile::exists( path ) ) { warning() << Q_FUNC_INFO << "file" << path << "does not exist, not inserting into db"; return false; } // -- the target path SqlRegistry *registry = m_collection->registry(); int deviceId = m_collection->mountPointManager()->getIdForUrl( QUrl::fromLocalFile( path ) ); QString rpath = m_collection->mountPointManager()->getRelativePath( deviceId, path ); int directoryId = registry->getDirectory( QFileInfo( path ).path() ); // -- the track uid (we can't use the original one from the old collection) Meta::FieldHash fileTags = Meta::Tag::readTags( path ); 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, &OrganizeCollectionDelegate::accepted, this, &SqlCollectionLocation::slotDialogAccepted ); connect( delegate, &OrganizeCollectionDelegate::rejected, this, &SqlCollectionLocation::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, 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()) ); + &SqlCollectionLocation::slotTransferJobAborted ); connect( m_transferjob, &Collections::TransferJob::result, this, &SqlCollectionLocation::slotTransferJobFinished ); 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(); QUrl src = m_sources.take( track ); QUrl dest = QUrl::fromLocalFile(m_destinations[ track ]); dest.setPath( QDir::cleanPath(dest.path()) ); src.setPath( QDir::cleanPath(src.path()) ); 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 ); if( !srcInfo.isFile() ) { warning() << "Source track" << src << "was no file"; source()->transferError( track, i18n( "Source track does not exist: %1", src.toDisplayString() ) ); return true; // Attempt to copy/move the next item in m_sources } QFileInfo destInfo( dest.toDisplayString() ); 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.matches( dest, QUrl::StripTrailingSlash ) ) { 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 ) { 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 ) { QUrl moodSrc = moodFile( src ); QUrl moodDest = moodFile( dest ); moodJob = KIO::file_copy( moodSrc, moodDest, -1, flags ); } } if( job ) //just to be safe { connect( job, &KJob::result, this, &SqlCollectionLocation::slotJobFinished ); connect( job, &KJob::result, m_transferjob, &Collections::TransferJob::slotJobFinished ); m_transferjob->addSubjob( job ); if( moodJob ) { connect( moodJob, &KJob::result, m_transferjob, &Collections::TransferJob::slotJobFinished ); 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(); // 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 == QUrl::fromUserInput(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; } QUrl SqlCollectionLocation::moodFile( const QUrl &track ) const { 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, &TransferJob::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/ipodcollection/IpodCollectionLocation.cpp b/src/core-impl/collections/ipodcollection/IpodCollectionLocation.cpp index aa5e951a40..d96c2a09c1 100644 --- a/src/core-impl/collections/ipodcollection/IpodCollectionLocation.cpp +++ b/src/core-impl/collections/ipodcollection/IpodCollectionLocation.cpp @@ -1,153 +1,154 @@ /**************************************************************************************** * 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( QPointer 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->prettyName(); // match string with IpodCopyTracksJob::slotDisplaySorryDialog() return i18n( "Disconnected iPod/iPad/iPhone" ); } bool IpodCollectionLocation::isWritable() const { if( !m_coll ) return false; return m_coll->isWritable(); // no infinite loop, IpodCollection iplements this } void 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()) ); + operationInProgressText( configuration, trackCount ), + trackCount, job, &IpodCopyTracksJob::abort ); qRegisterMetaType( "IpodCopyTracksJob::CopiedStatus" ); connect( job, &IpodCopyTracksJob::signalTrackProcessed, this, &IpodCollectionLocation::slotCopyTrackProcessed ); connect( job, &IpodCopyTracksJob::done, this, &IpodCollectionLocation::slotCopyOperationFinished ); connect( job, &IpodCopyTracksJob::done, job, &QObject::deleteLater ); ThreadWeaver::Queue::instance()->enqueue( QSharedPointer(job) ); } void IpodCollectionLocation::removeUrlsFromCollection( const Meta::TrackList &sources ) { if( !isWritable() ) return; IpodDeleteTracksJob *job = new IpodDeleteTracksJob( sources, m_coll ); connect( job, &IpodDeleteTracksJob::done, this, &IpodCollectionLocation::slotRemoveOperationFinished ); connect( job, &IpodDeleteTracksJob::done, job, &QObject::deleteLater ); ThreadWeaver::Queue::instance()->enqueue( QSharedPointer(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->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/jobs/IpodParseTracksJob.cpp b/src/core-impl/collections/ipodcollection/jobs/IpodParseTracksJob.cpp index 16b87f165e..0fa804359e 100644 --- a/src/core-impl/collections/ipodcollection/jobs/IpodParseTracksJob.cpp +++ b/src/core-impl/collections/ipodcollection/jobs/IpodParseTracksJob.cpp @@ -1,185 +1,185 @@ /**************************************************************************************** * 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 #include IpodParseTracksJob::IpodParseTracksJob( IpodCollection *collection ) : QObject() , ThreadWeaver::Job() , m_coll( collection ) , m_aborted( false ) { } void IpodParseTracksJob::abort() { m_aborted = true; } void IpodParseTracksJob::run(ThreadWeaver::JobPointer self, ThreadWeaver::Thread *thread) { Q_UNUSED(self); Q_UNUSED(thread); 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()) ); + this, &IpodParseTracksJob::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::defaultBegin(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread) { Q_EMIT started(self); ThreadWeaver::Job::defaultBegin(self, thread); } void IpodParseTracksJob::defaultEnd(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread) { ThreadWeaver::Job::defaultEnd(self, thread); if (!self->success()) { Q_EMIT failed(self); } Q_EMIT done(self); } 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( QUrl::fromLocalFile( canonPath ) ) ); orphanedTracks << track; } } return orphanedTracks; } diff --git a/src/core-impl/collections/support/CollectionManager.cpp b/src/core-impl/collections/support/CollectionManager.cpp index 988cea4796..cd8cc91fc8 100644 --- a/src/core-impl/collections/support/CollectionManager.cpp +++ b/src/core-impl/collections/support/CollectionManager.cpp @@ -1,426 +1,422 @@ /**************************************************************************************** * 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-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 > 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 ); while (!d->collections.isEmpty() ) delete d->collections.takeFirst().first; 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 matches two tracks d->fileTrackProvider = new FileTrackProvider(); addTrackProvider( d->fileTrackProvider ); } void -CollectionManager::setFactories( const QList &factories ) +CollectionManager::setFactories( const QList > &factories ) { using Collections::CollectionFactory; - QSet newFactories = factories.toSet(); - QSet oldFactories; + QSet > newFactories = factories.toSet(); + QSet > oldFactories; { QReadLocker locker( &d->lock ); oldFactories = d->factories.toSet(); } // remove old factories - foreach( Plugins::PluginFactory* pFactory, oldFactories - newFactories ) + for( const auto &pFactory : oldFactories - newFactories ) { - CollectionFactory *factory = qobject_cast( pFactory ); + auto factory = qobject_cast( pFactory ); if( !factory ) continue; - disconnect( factory, &CollectionFactory::newCollection, + disconnect( factory.data(), &CollectionFactory::newCollection, this, &CollectionManager::slotNewCollection ); { QWriteLocker locker( &d->lock ); d->factories.removeAll( factory ); } } // create new factories - foreach( Plugins::PluginFactory* pFactory, newFactories - oldFactories ) + for( const auto &pFactory : newFactories - oldFactories ) { - CollectionFactory *factory = qobject_cast( pFactory ); + auto factory = qobject_cast( pFactory ); if( !factory ) continue; - connect( factory, &CollectionFactory::newCollection, + connect( factory.data(), &CollectionFactory::newCollection, this, &CollectionManager::slotNewCollection ); { QWriteLocker locker( &d->lock ); d->factories.append( factory ); } } } 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 = Amarok::config( "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( 1, 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, &Collections::Collection::remove, this, &CollectionManager::slotRemoveCollection, Qt::QueuedConnection ); connect( newCollection, &Collections::Collection::updated, this, &CollectionManager::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, &QObject::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 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.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 3314b88284..8e8da25157 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 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 useful functions are probably queryMaker and * viewableCollections */ class AMAROK_EXPORT CollectionManager : public QObject { Q_OBJECT 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 }; Q_ENUM( CollectionStatus ) 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 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 ); + void setFactories( const QList > &factories ); public Q_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(); Q_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 Q_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/umscollection/UmsCollectionLocation.cpp b/src/core-impl/collections/umscollection/UmsCollectionLocation.cpp index fa21ce63fe..cfed768a48 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 UmsCollectionLocation::UmsCollectionLocation( UmsCollection *umsCollection ) : CollectionLocation( umsCollection ) , m_umsCollection( umsCollection ) { } UmsCollectionLocation::~UmsCollectionLocation() { } QString UmsCollectionLocation::prettyLocation() const { return m_umsCollection->musicUrl().adjusted(QUrl::StripTrailingSlash).toLocalFile(); } QStringList UmsCollectionLocation::actualLocation() const { return QStringList() << prettyLocation(); } bool UmsCollectionLocation::isWritable() const { const QFileInfo info( m_umsCollection->musicUrl().toLocalFile() ); return info.isWritable(); } bool UmsCollectionLocation::isOrganizable() const { return isWritable(); } void 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 ); while( i.hasNext() ) { i.next(); Meta::TrackPtr track = i.key(); 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.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, &UmsTransferJob::sourceFileTransferDone, this, &UmsCollectionLocation::slotTrackTransferred ); connect( transferJob, &UmsTransferJob::fileTransferDone, m_umsCollection, &UmsCollection::slotTrackAdded ); connect( transferJob, &UmsTransferJob::finished, this, &UmsCollectionLocation::slotCopyOperationFinished ); QString loggerText = operationInProgressText( configuration, sources.count(), m_umsCollection->prettyName() ); Amarok::Components::logger()->newProgressOperation( transferJob, loggerText, transferJob, - SLOT(slotCancel()) ); + &UmsTransferJob::slotCancel ); transferJob->start(); } void UmsCollectionLocation::removeUrlsFromCollection( const Meta::TrackList &sources ) { QList sourceUrls; foreach( const Meta::TrackPtr track, sources ) { 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()) ); + Amarok::Components::logger()->newProgressOperation( delJob, loggerText, delJob, &KIO::DeleteJob::kill, Qt::AutoConnection, KIO::Job::Quietly ); connect( delJob, &KIO::DeleteJob::finished, this, &UmsCollectionLocation::slotRemoveOperationFinished ); } void 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 ) { 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 QUrl &from, const QUrl &to ) { m_copyList << KUrlPair( from, to ); } void 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/logger/ProxyLogger.cpp b/src/core-impl/logger/ProxyLogger.cpp index 6900b29e49..237e0c7085 100644 --- a/src/core-impl/logger/ProxyLogger.cpp +++ b/src/core-impl/logger/ProxyLogger.cpp @@ -1,186 +1,188 @@ /**************************************************************************************** * Copyright (c) 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 . * ****************************************************************************************/ #include "ProxyLogger.h" #include #include #include ProxyLogger::ProxyLogger() : Amarok::Logger() , m_logger( 0 ) , m_timer( 0 ) { // ensure that the object livs in the GUI thread Q_ASSERT( thread() == QCoreApplication::instance()->thread() ); m_timer = new QTimer( this ); connect( m_timer, &QTimer::timeout, this, &ProxyLogger::forwardNotifications ); m_timer->setSingleShot( true ); m_timer->setInterval( 0 ); connect( this, &ProxyLogger::startTimer, this, &ProxyLogger::slotStartTimer ); } ProxyLogger::~ProxyLogger() { //nothing to do } void ProxyLogger::setLogger( Amarok::Logger *logger ) { m_logger = logger; emit startTimer(); } Amarok::Logger * ProxyLogger::logger() const { return m_logger; } void ProxyLogger::slotStartTimer() { if( m_logger && !m_timer->isActive() ) m_timer->start(); } void ProxyLogger::shortMessage( const QString &text ) { QMutexLocker locker( &m_lock ); m_shortMessageQueue.enqueue( text ); emit startTimer(); } void ProxyLogger::longMessage( const QString &text, MessageType type ) { QMutexLocker locker( &m_lock ); LongMessage msg; msg.first = text; msg.second = type; m_longMessageQueue.enqueue( msg ); emit startTimer(); } -void -ProxyLogger::newProgressOperation( KJob *job, const QString &text, QObject *obj, const char *slot, Qt::ConnectionType type ) +void ProxyLogger::newProgressOperationImpl( KJob* job, const QString& text, QObject* context, const std::function &function, Qt::ConnectionType type ) { QMutexLocker locker( &m_lock ); ProgressData data; data.job = job; data.text = text; - data.cancelObject = obj; - data.slot = slot; + data.cancelObject = context; + data.function = function; data.type = type; m_progressQueue.enqueue( data ); emit startTimer(); } void -ProxyLogger::newProgressOperation( QNetworkReply *reply, const QString &text, QObject *obj, const char *slot, Qt::ConnectionType type ) +ProxyLogger::newProgressOperationImpl( QNetworkReply *reply, const QString &text, QObject *obj, const std::function &function, Qt::ConnectionType type ) { QMutexLocker locker( &m_lock ); ProgressData data; data.reply = reply; data.text = text; data.cancelObject = obj; - data.slot = slot; + data.function = function; data.type = type; m_progressQueue.enqueue( data ); emit startTimer(); } void -ProxyLogger::newProgressOperation( QObject *sender, const QString &text, int maximum, QObject *obj, - const char *slot, Qt::ConnectionType type ) +ProxyLogger::newProgressOperationImpl( QObject *sender, const QMetaMethod &increment, const QMetaMethod &end, const QString &text, + int maximum, QObject *obj, const std::function &function, Qt::ConnectionType type ) { QMutexLocker locker( &m_lock ); ProgressData data; data.sender = sender; + data.increment = increment; + data.end = end; data.text = text; data.maximum = maximum; data.cancelObject = obj; - data.slot = slot; + data.function = function; data.type = type; m_progressQueue.enqueue( data ); - connect( sender, SIGNAL(totalSteps(int)), SLOT(slotTotalSteps(int)) ); + if( sender->staticMetaObject.indexOfSignal( SIGNAL(totalSteps(int)) ) != -1 ) + connect( sender, SIGNAL(totalSteps(int)), SLOT(slotTotalSteps(int)) ); emit startTimer(); } void ProxyLogger::forwardNotifications() { QMutexLocker locker( &m_lock ); if( !m_logger ) return; //can't do anything before m_logger is created. while( !m_shortMessageQueue.isEmpty() ) { m_logger->shortMessage( m_shortMessageQueue.dequeue() ); } while( !m_longMessageQueue.isEmpty() ) { LongMessage msg = m_longMessageQueue.dequeue(); m_logger->longMessage( msg.first, msg.second ); } while( !m_progressQueue.isEmpty() ) { ProgressData d = m_progressQueue.dequeue(); if( d.job ) { - m_logger->newProgressOperation( d.job.data(), d.text, d.cancelObject.data(), - d.cancelObject.data() ? d.slot : 0 , d.type ); + m_logger->newProgressOperationImpl( d.job.data(), d.text, d.cancelObject.data(), + d.function, d.type ); } else if( d.reply ) { - m_logger->newProgressOperation( d.reply.data(), d.text, d.cancelObject.data(), - d.cancelObject.data() ? d.slot : 0 , d.type ); + m_logger->newProgressOperationImpl( d.reply.data(), d.text, d.cancelObject.data(), + d.function, d.type ); } else if( d.sender ) { // m_logger handles the signals from now on disconnect( d.sender.data(), 0, this, 0 ); - m_logger->newProgressOperation( d.sender.data(), d.text, d.maximum, - d.cancelObject.data(), - d.cancelObject.data() ? d.slot : 0 , d.type ); + m_logger->newProgressOperationImpl( d.sender.data(), d.increment, d.end, d.text, + d.maximum, d.cancelObject.data(), + d.function, d.type ); } } } void ProxyLogger::slotTotalSteps( int totalSteps ) { QObject *operation = sender(); if( !operation ) // warning, slotTotalSteps can only be connected to progress operation QObject signal return; QMutableListIterator it( m_progressQueue ); while( it.hasNext() ) { ProgressData &data = it.next(); if( data.sender.data() != operation ) continue; data.maximum = totalSteps; return; } // warning, operation not found in m_progressQueue } diff --git a/src/core-impl/logger/ProxyLogger.h b/src/core-impl/logger/ProxyLogger.h index 141b97eaf0..0302e33715 100644 --- a/src/core-impl/logger/ProxyLogger.h +++ b/src/core-impl/logger/ProxyLogger.h @@ -1,108 +1,112 @@ /**************************************************************************************** * Copyright (c) 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 . * ****************************************************************************************/ #ifndef AMAROK_PROXY_LOGGER_H #define AMAROK_PROXY_LOGGER_H #include "core/interfaces/Logger.h" #include #include #include #include #include #include #include +#include + + class QNetworkReply; typedef QPair LongMessage; struct ProgressData { QPointer sender; + QMetaMethod increment; + QMetaMethod end; QPointer job; QPointer reply; QString text; int maximum; QPointer cancelObject; - const char *slot; + std::function function; Qt::ConnectionType type; }; /** * Proxy implementation for the Amarok::Logger interface. * This class does not notify the user, but forwards the notifications * to a real logger if available. If no logger is available yet, it stores * the notifications until another logger becomes available. * * This class can be only instantiated from the main thread and must reside in the main * thread for its lifetime. */ class ProxyLogger : public QObject, public Amarok::Logger { Q_OBJECT Q_PROPERTY( Amarok::Logger* logger READ logger WRITE setLogger DESIGNABLE false ) public: ProxyLogger(); virtual ~ProxyLogger(); public Q_SLOTS: virtual void shortMessage( const QString &text ); virtual void longMessage( const QString &text, MessageType type ); - virtual void newProgressOperation( KJob *job, const QString &text, QObject *obj = 0, - const char *slot = 0, - Qt::ConnectionType type = Qt::AutoConnection ); - virtual void newProgressOperation( QNetworkReply *reply, const QString &text, QObject *obj = 0, - const char *slot = 0, - Qt::ConnectionType type = Qt::AutoConnection ); - virtual void newProgressOperation( QObject *sender, const QString &text, int maximum = 100, - QObject *obj = 0, const char *slot = 0, - Qt::ConnectionType type = Qt::AutoConnection ); + virtual void newProgressOperationImpl( KJob * job, const QString & text, QObject * context, + const std::function &function, Qt::ConnectionType type ) override; + virtual void newProgressOperationImpl( QNetworkReply *reply, const QString &text, QObject *obj, + const std::function &function, + Qt::ConnectionType type ) override; + virtual void newProgressOperationImpl( QObject *sender, const QMetaMethod &increment, const QMetaMethod &end, + const QString &text, int maximum, QObject *obj, + const std::function &function, Qt::ConnectionType type ) override; /** * Set the real logger. * The proxy logger will forward notifications to this logger. * @param logger The real logger to use. ProxyLogger does not take ownership of the pointer */ void setLogger( Logger *logger ); Logger* logger() const; private Q_SLOTS: void forwardNotifications(); void slotStartTimer(); void slotTotalSteps( int totalSteps ); Q_SIGNALS: // timer can only be started from its thread, use signals & slots to pass thread barrier void startTimer(); private: Logger *m_logger; //!< stores the real logger QMutex m_lock; //!< protect members that may be accessed from multiple threads QTimer *m_timer; //!< internal timer that triggers forwarding of notifications QQueue m_shortMessageQueue; //!< temporary storage for notifications that have not been forwarded yet QQueue m_longMessageQueue; //!< temporary storage for notifications that have not been forwarded yet QQueue m_progressQueue; //!< temporary storage for notifications that have not been forwarded yet }; Q_DECLARE_METATYPE(ProxyLogger *) #endif diff --git a/src/core-impl/podcasts/sql/SqlPodcastProvider.cpp b/src/core-impl/podcasts/sql/SqlPodcastProvider.cpp index c42ca3e60a..61d9deb12b 100644 --- a/src/core-impl/podcasts/sql/SqlPodcastProvider.cpp +++ b/src/core-impl/podcasts/sql/SqlPodcastProvider.cpp @@ -1,1613 +1,1615 @@ /**************************************************************************************** * 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 #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, &QTimer::timeout, this, &SqlPodcastProvider::autoUpdate ); auto 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 = QUrl::fromUserInput( 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(); auto 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(); auto 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 QUrl &url ) const { auto 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 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( QIcon::fromTheme( "view-refresh-amarok" ), i18n( "&Update All Channels" ), this ); updateAllAction->setProperty( "popupdropper_svg_id", "update" ); connect( updateAllAction, &QAction::triggered, this, &SqlPodcastProvider::updateAll ); m_providerActions << updateAllAction; QAction *configureAction = new QAction( QIcon::fromTheme( "configure" ), i18n( "&Configure General Settings" ), this ); configureAction->setProperty( "popupdropper_svg_id", "configure" ); connect( configureAction, &QAction::triggered, this, &SqlPodcastProvider::slotConfigureProvider ); m_providerActions << configureAction; QAction *exportOpmlAction = new QAction( QIcon::fromTheme( "document-export" ), i18n( "&Export subscriptions to OPML file" ), this ); connect( exportOpmlAction, &QAction::triggered, this, &SqlPodcastProvider::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( QIcon::fromTheme( "configure" ), i18n( "&Configure" ), this ); m_configureChannelAction->setProperty( "popupdropper_svg_id", "configure" ); connect( m_configureChannelAction, &QAction::triggered, this, &SqlPodcastProvider::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( QIcon::fromTheme( "news-unsubscribe" ), i18n( "&Remove Subscription" ), this ); m_removeAction->setProperty( "popupdropper_svg_id", "remove" ); connect( m_removeAction, &QAction::triggered, this, &SqlPodcastProvider::slotRemoveChannels ); } m_removeAction->setData( QVariant::fromValue( sqlChannels ) ); actions << m_removeAction; if( m_updateAction == 0 ) { m_updateAction = new QAction( QIcon::fromTheme( "view-refresh-amarok" ), i18n( "&Update Channel" ), this ); m_updateAction->setProperty( "popupdropper_svg_id", "update" ); connect( m_updateAction, &QAction::triggered, this, &SqlPodcastProvider::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( QIcon::fromTheme( "go-down" ), i18n( "&Download Episode" ), this ); m_downloadAction->setProperty( "popupdropper_svg_id", "download" ); connect( m_downloadAction, &QAction::triggered, this, &SqlPodcastProvider::slotDownloadEpisodes ); } if( m_deleteAction == 0 ) { m_deleteAction = new QAction( QIcon::fromTheme( "edit-delete" ), i18n( "&Delete Downloaded Episode" ), this ); m_deleteAction->setProperty( "popupdropper_svg_id", "delete" ); m_deleteAction->setObjectName( "deleteAction" ); connect( m_deleteAction, &QAction::triggered, this, &SqlPodcastProvider::slotDeleteDownloadedEpisodes ); } if( m_writeTagsAction == 0 ) { m_writeTagsAction = new QAction( QIcon::fromTheme( "media-track-edit-amarok" ), i18n( "&Write Feed Information to File" ), this ); m_writeTagsAction->setProperty( "popupdropper_svg_id", "edit" ); connect( m_writeTagsAction, &QAction::triggered, this, &SqlPodcastProvider::slotWriteTagsToFiles ); } if( m_keepAction == 0 ) { m_keepAction = new QAction( QIcon::fromTheme( "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, &QAction::triggered, this, &SqlPodcastProvider::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 QUrl &url ) { QUrl kurl = QUrl( url ); debug() << "importing " << kurl.url(); auto 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 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, &PodcastReader::finished, this, &SqlPodcastProvider::slotReadResult ); connect( podcastReader, &PodcastReader::statusBarSorryMessage, this, &SqlPodcastProvider::slotStatusBarSorryMessage ); connect( podcastReader, &PodcastReader::statusBarNewProgressOperation, this, &SqlPodcastProvider::slotStatusBarNewProgressOperation ); 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() ) { auto 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 QDialog( 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( i18np( " minute", " minutes", settings.m_autoUpdateInterval->value() ) ); auto buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Apply, m_providerSettingsDialog ); connect( settings.m_baseDirUrl, &KUrlRequester::textChanged, this, &SqlPodcastProvider::slotConfigChanged ); connect( settings.m_autoUpdateInterval, QOverload::of(&QSpinBox::valueChanged), this, &SqlPodcastProvider::slotConfigChanged ); m_providerSettingsDialog->setWindowTitle( i18n( "Configure Local Podcasts" ) ); buttonBox->button( QDialogButtonBox::Apply )->setEnabled( false ); if( m_providerSettingsDialog->exec() == QDialog::Accepted ) { m_autoUpdateInterval = settings.m_autoUpdateInterval->value(); if( m_autoUpdateInterval ) startTimer(); else m_updateTimer->stop(); QUrl adjustedNewPath = settings.m_baseDirUrl->url(); 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 auto button = QMessageBox::question( The::mainWindow(), i18n( "Move Podcasts" ), i18n( "Do you want to move all downloaded episodes to the new location?") ); if( button == QMessageBox::Yes ) { foreach( SqlPodcastChannelPtr sqlChannel, m_channels ) { 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 ) { auto buttonBox = m_providerSettingsDialog->findChild(); buttonBox->button( QDialogButtonBox::Apply )->setEnabled( 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. QFileDialog fileDialog; fileDialog.restoreState( Amarok::config( "amarok-podcast-export-dialog" ).readEntry( "state", QByteArray() ) ); fileDialog.setMimeTypeFilters( QStringList( QStringLiteral( "*.opml" ) ) ); fileDialog.setAcceptMode( QFileDialog::AcceptSave ); fileDialog.setFileMode( QFileDialog::AnyFile ); fileDialog.setWindowTitle( i18n( "Select file for OPML export") ); if( fileDialog.exec() != QDialog::Accepted ) return; QString filePath = fileDialog.selectedFiles().value( 0 ); QFile *opmlFile = new QFile( filePath, this ); if( !opmlFile->open( QIODevice::WriteOnly | QIODevice::Truncate ) ) { error() << "could not open OPML file " << filePath; return; } OpmlWriter *opmlWriter = new OpmlWriter( rootOutlines, headerData, opmlFile ); connect( opmlWriter, &OpmlWriter::result, this, &SqlPodcastProvider::slotOpmlWriterDone ); 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; 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(), sqlChannel->saveLocation().toDisplayString() ); QList filesToMove; foreach( Podcasts::SqlPodcastEpisodePtr episode, sqlChannel->sqlEpisodes() ) { if( !episode->localUrl().isEmpty() ) { QUrl newLocation = sqlChannel->saveLocation(); QDir dir( newLocation.toLocalFile() ); dir.mkpath( "." ); 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( moveJob->exec() ) 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 ) { QMessageBox unsubscribeDialog; unsubscribeDialog.setText( i18n( "Do you really want to unsubscribe from \"%1\"?", channel->title() ) ); unsubscribeDialog.setStandardButtons( QMessageBox::Ok | QMessageBox::Cancel ); QCheckBox *deleteMediaCheckBox = new QCheckBox( i18n( "Delete downloaded episodes" ), Q_NULLPTR ); unsubscribeDialog.setCheckBox( deleteMediaCheckBox ); QPair result; result.first = unsubscribeDialog.exec() == QMessageBox::Ok; 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( 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() ); QProgressDialog progressDialog( i18np( "There is still a podcast download in progress", "There are still %1 podcast downloads in progress", m_downloadJobMap.count() ), i18n("Cancel Download and Quit."), 0, m_downloadJobMap.size(), The::mainWindow() ); progressDialog.setValue( 0 ); m_completedDownloads = 0; foreach( KJob *job, m_downloadJobMap.keys() ) { connect( job, SIGNAL(percent(KJob*,ulong)), this, SLOT(slotDownloadProgress(KJob*,ulong)) ); } connect( this, &SqlPodcastProvider::totalPodcastDownloadProgress, &progressDialog, &QProgressDialog::setValue ); int result = progressDialog.exec(); if( result == QDialog::Rejected ) { foreach( KJob *job, m_downloadJobMap.keys() ) { job->kill(); } } } } void SqlPodcastProvider::autoUpdate() { QNetworkConfigurationManager mgr; if( !mgr.isOnline() ) { 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, &PodcastReader::finished, this, &SqlPodcastProvider::slotReadResult ); connect( podcastReader, &PodcastReader::statusBarSorryMessage, this, &SqlPodcastProvider::slotStatusBarSorryMessage ); connect( podcastReader, &PodcastReader::statusBarNewProgressOperation, this, &SqlPodcastProvider::slotStatusBarNewProgressOperation ); 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()) ); + Amarok::Components::logger()->newProgressOperation( job, description, reader, &Podcasts::PodcastReader::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( QUrl::fromUserInput(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() */ 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()) + &KIO::TransferJob::kill, + Qt::AutoConnection, + KJob::Quietly ); connect( transferJob, &KIO::TransferJob::data, this, &SqlPodcastProvider::addData ); //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, &KIO::TransferJob::finished, this, &SqlPodcastProvider::downloadResult ); connect( transferJob, &KIO::TransferJob::redirection, this, &SqlPodcastProvider::redirected ); } 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? 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( QCryptographicHash::hash( tempName.toUtf8(), QCryptographicHash::Md5 ).toHex() ); 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().adjusted(QUrl::StripTrailingSlash).toLocalFile(); 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( QUrl::fromLocalFile(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 = Amarok::QStringx(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().adjusted(QUrl::StripTrailingSlash).toLocalFile() + download.fileName; if( tmpFile->rename( finalName ) ) { debug() << "successfully written Podcast Episode " << sqlEpisode->title() << " to " << finalName; sqlEpisode->setLocalUrl( QUrl::fromLocalFile(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 QUrl &redirectedUrl ) { debug() << "redirecting to " << redirectedUrl << ". filename: " << redirectedUrl.fileName(); m_downloadJobMap[job].fileName = redirectedUrl.fileName(); } void SqlPodcastProvider::createTables() const { auto 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 ); auto 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, &PodcastImageFetcher::channelImageReady, this, &SqlPodcastProvider::channelImageReady ); connect( m_podcastImageFetcher,&PodcastImageFetcher::done, this, &SqlPodcastProvider::podcastImageFetcherDone ); } 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/storage/StorageManager.cpp b/src/core-impl/storage/StorageManager.cpp index 7690119eb8..2380aee915 100644 --- a/src/core-impl/storage/StorageManager.cpp +++ b/src/core-impl/storage/StorageManager.cpp @@ -1,202 +1,202 @@ /**************************************************************************************** * Copyright (c) 2014 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 "StorageManager" #include "StorageManager.h" #include #include #include #include #include #include /** A SqlStorage that doesn't do anything. * * An object of this type is used whenever we couldn't * load a better SqlStorage. * * The reason is that plugins don't have to check for * a null pointer as SqlStorage every time. */ class EmptySqlStorage : public SqlStorage { public: EmptySqlStorage() {} virtual ~EmptySqlStorage() {} virtual int sqlDatabasePriority() const { return 10; } virtual QString type() const { return QLatin1String("Empty"); } virtual QString escape( const QString &text) const { return text; } virtual QStringList query( const QString &) { return QStringList(); } virtual int insert( const QString &, const QString &) { return 0; } virtual QString boolTrue() const { return QString(); } virtual QString boolFalse() const { return QString(); } virtual QString idType() const { return QString(); }; virtual QString textColumnType( int ) const { return QString(); } virtual QString exactTextColumnType( int ) const { return QString(); } virtual QString exactIndexableTextColumnType( int ) const { return QString(); } virtual QString longTextColumnType() const { return QString(); } virtual QString randomFunc() const { return QString(); } virtual QStringList getLastErrors() const { return QStringList(); } /** Clears the list of the last errors. */ virtual void clearLastErrors() { } }; struct StorageManager::Private { QSharedPointer sqlDatabase; /** A list that collects errors from database plugins * * StoragePlugin factories can report errors that * prevent the storage from even being created. * * This list collects them. */ QStringList errorList; }; StorageManager *StorageManager::s_instance = Q_NULLPTR; StorageManager * StorageManager::instance() { if( !s_instance ) { s_instance = new StorageManager(); s_instance->init(); } return s_instance; } void StorageManager::destroy() { if( s_instance ) { delete s_instance; s_instance = 0; } } StorageManager::StorageManager() : QObject() , d( new Private ) { DEBUG_BLOCK setObjectName( "StorageManager" ); qRegisterMetaType( "SqlStorage*" ); d->sqlDatabase = QSharedPointer( new EmptySqlStorage ); } StorageManager::~StorageManager() { DEBUG_BLOCK delete d; } QSharedPointer StorageManager::sqlStorage() const { return d->sqlDatabase; } void StorageManager::init() { } void -StorageManager::setFactories( const QList &factories ) +StorageManager::setFactories( const QList > &factories ) { - foreach( Plugins::PluginFactory* pFactory, factories ) + for( const auto &pFactory : factories ) { - StorageFactory *factory = qobject_cast( pFactory ); + auto factory = qobject_cast( pFactory ); if( !factory ) continue; - connect( factory, &StorageFactory::newStorage, + connect( factory.data(), &StorageFactory::newStorage, this, &StorageManager::slotNewStorage ); - connect( factory, &StorageFactory::newError, + connect( factory.data(), &StorageFactory::newError, this, &StorageManager::slotNewError ); } } QStringList StorageManager::getLastErrors() const { if( !d->errorList.isEmpty() ) return d->errorList; if( d->sqlDatabase.dynamicCast() ) { QStringList list; list << i18n( "The configured database plugin could not be loaded." ); return list; } return d->errorList; } void StorageManager::clearLastErrors() { d->errorList.clear(); } void StorageManager::slotNewStorage( QSharedPointer newStorage ) { DEBUG_BLOCK if( !newStorage ) { warning() << "Warning, newStorage in slotNewStorage is 0"; return; } if( d->sqlDatabase && !d->sqlDatabase.dynamicCast() ) { warning() << "Warning, newStorage when we already have a storage"; return; // once we have the database set we can't change it since // plugins might have already created their tables in the old database // or caching data from it. } d->sqlDatabase = newStorage; } void StorageManager::slotNewError( QStringList errorMessageList ) { d->errorList << errorMessageList; } diff --git a/src/core-impl/storage/StorageManager.h b/src/core-impl/storage/StorageManager.h index 9d89b32aea..94a9c2735f 100644 --- a/src/core-impl/storage/StorageManager.h +++ b/src/core-impl/storage/StorageManager.h @@ -1,122 +1,121 @@ /**************************************************************************************** * Copyright (c) 2014 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_STORAGEMANAGER_H #define AMAROK_STORAGEMANAGER_H #include "amarok_export.h" #include #include #include #include namespace Plugins { class PluginFactory; } class SqlStorage; /** Class managing the Amarok SqlStorage * * This singleton class is the main responsible for providing everybody * with the current SqlStorage. */ class AMAROK_EXPORT StorageManager : public QObject { Q_OBJECT public: /** Get THE instance of the storage manager. * * This function will return the storage manager * that returns the sql storage to be used for Amarok. * * In addition to the SqlCollection a lot of other components * use a sql storage to persist data. * */ static StorageManager *instance(); /** Destroys the instance of the StorageManager. */ static void destroy(); /** retrieve an interface which allows client-code to store/load data in a relational database. - Note: code using this method does NOT take ownership of the pointer, but may cache the pointer - Note2: You should never modify the database unless you really really know what you do. + Note: You should never modify the database unless you really really know what you do. Using the SqlMeta (e.g. SqlRegistry or SqlTrack) is much better. @return Returns a pointer to the amarok wide SqlStorage or to an internal dummy SqlStorage if that cannot be found. It never returns a null pointer. */ QSharedPointer sqlStorage() const; /** * 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 ); + void setFactories( const QList > &factories ); /** Returns a list of the last sql errors. The list might not include every one error if the number is beyond a sensible limit. */ QStringList getLastErrors() const; /** Clears the list of the last errors. */ void clearLastErrors(); private Q_SLOTS: /** Will be called whenever a factory emits a newStorage signal. * * The first factory to emit this signal will get it's storage * registered as "the" storage. * * StorageManager will take ownership of the pointer and free it * after all other plugins are done. */ void slotNewStorage( QSharedPointer newStorage ); /** Will be called whenever a factory emits a newError signal. * * The factories will not emit the newStorage signal in case * of initialization problems. * In order to report their issues they will instead emit * newError with the list of errors. */ void slotNewError( QStringList errorMessageList ); private: static StorageManager* s_instance; StorageManager(); ~StorageManager(); void init(); Q_DISABLE_COPY( StorageManager ) struct Private; Private * const d; }; #endif /* AMAROK_STORAGEMANAGER_H */ diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index a93c3f3055..24e9b0f693 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1,126 +1,125 @@ set(libcore_interfaces_SRCS interfaces/Logger.cpp interfaces/MetaCapability.cpp ) set(libcore_meta_SRCS meta/Base.cpp meta/Meta.cpp meta/Observer.cpp meta/Statistics.cpp meta/TrackEditor.cpp meta/support/MetaConstants.cpp meta/support/MetaUtility.cpp meta/support/MetaKeys.cpp meta/support/PrivateMetaRegistry.cpp ) set(libcore_playlists_SRCS playlists/Playlist.cpp playlists/PlaylistFormat.cpp playlists/PlaylistProvider.cpp ) set(libcore_capabilities_SRCS capabilities/Capability.cpp capabilities/ActionsCapability.cpp capabilities/BookmarkThisCapability.cpp capabilities/BoundedPlaybackCapability.cpp capabilities/CollectionScanCapability.cpp capabilities/CollectionImportCapability.cpp capabilities/FindInSourceCapability.cpp capabilities/MultiPlayableCapability.cpp capabilities/MultiSourceCapability.cpp capabilities/OrganiseCapability.cpp capabilities/ReadLabelCapability.cpp capabilities/SourceInfoCapability.cpp capabilities/StreamInfoCapability.cpp capabilities/TranscodeCapability.cpp capabilities/WriteLabelCapability.cpp ) set(libcore_collection_SRCS collections/Collection.cpp collections/CollectionLocation.cpp collections/MetaQueryMaker.cpp collections/QueryMaker.cpp collections/support/TrackForUrlWorker.cpp ) set(libcore_storage_SRCS storage/StorageFactory.cpp ) set(libcore_podcasts_SRCS podcasts/PodcastReader.cpp podcasts/PodcastMeta.cpp podcasts/PodcastImageFetcher.cpp podcasts/PodcastProvider.cpp ) set(libcore_support_SRCS support/Amarok.cpp support/Components.cpp support/SemaphoreReleaser.cpp - support/SmartPointerList.cpp support/PluginFactory.cpp support/Debug.cpp ) set(libcore_transcoding_SRCS transcoding/formats/TranscodingNullFormat.cpp transcoding/formats/TranscodingAacFormat.cpp transcoding/formats/TranscodingAlacFormat.cpp transcoding/formats/TranscodingFlacFormat.cpp transcoding/formats/TranscodingMp3Format.cpp transcoding/formats/TranscodingOpusFormat.cpp transcoding/formats/TranscodingVorbisFormat.cpp transcoding/formats/TranscodingWmaFormat.cpp transcoding/TranscodingConfiguration.cpp transcoding/TranscodingController.cpp transcoding/TranscodingProperty.cpp ) ##################################################################### # LIBCORE ##################################################################### set(libcore_LIB_SRCS ${libcore_podcasts_SRCS} ${libcore_interfaces_SRCS} ${libcore_collection_SRCS} ${libcore_storage_SRCS} ${libcore_playlists_SRCS} ${libcore_meta_SRCS} ${libcore_capabilities_SRCS} ${libcore_support_SRCS} ${libcore_transcoding_SRCS} ) add_library(amarokcore SHARED ${libcore_LIB_SRCS}) target_include_directories( amarokcore PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/support ${CMAKE_CURRENT_SOURCE_DIR}/meta ) target_link_libraries(amarokcore amarokshared ${CMAKE_DL_LIBS} Threads::Threads Qt5::Core Qt5::Network KF5::KIOCore KF5::ConfigCore KF5::ThreadWeaver KF5::I18n KF5::XmlGui ) if(APPLE) set_target_properties(amarokcore PROPERTIES LINK_FLAGS "-undefined dynamic_lookup") endif() set_target_properties(amarokcore PROPERTIES VERSION 1.0.0 SOVERSION 1 ) install(TARGETS amarokcore ${INSTALL_TARGETS_DEFAULT_ARGS} ) diff --git a/src/core/interfaces/Logger.h b/src/core/interfaces/Logger.h index 9177ffd74b..406f9eac49 100644 --- a/src/core/interfaces/Logger.h +++ b/src/core/interfaces/Logger.h @@ -1,112 +1,144 @@ /**************************************************************************************** * Copyright (c) 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 . * ****************************************************************************************/ #ifndef AMAROK_LOGGER_H #define AMAROK_LOGGER_H #include "core/amarokcore_export.h" -#include +#include #include +#include + + class KJob; class QNetworkReply; namespace Amarok { /** * This interface provides methods that allow backend components to notify the user. * Users of this class may not make assumptions about the kind of notifications that * will be sent to the user. * * The class name is up for discussion btw. */ class AMAROK_CORE_EXPORT Logger { public: enum MessageType { Information, Warning, Error }; Logger() {} virtual ~Logger() {} - public Q_SLOTS: - /** * Informs the user about the progress of a job, i.e. a download job. * At the very least, the user is notified about the start and end of the job. * * @param job The job whose progress should be monitored * @param text An additional text that will be part of the notification * @param obj The object that will be called if the user cancels the job. If not set, the job will not be cancellable * @param slot The slot on the given object that will be called if the user cancels the job. No slot will be called if not set. * The signal will be emitted from the GUI thread. The receiver may not make assumptions about the sender * @param type The Qt connection type to use for the connection to the receiving slot. Defaults to Qt::AutoConnection */ - virtual void newProgressOperation( KJob *job, const QString &text, QObject *obj = 0, const char *slot = 0, Qt::ConnectionType type = Qt::AutoConnection ) = 0; + template + void newProgressOperation( KJob *job, const QString &text, Object *obj = nullptr, Func slot = nullptr, Qt::ConnectionType type = Qt::AutoConnection, FuncArgs... args ) + { + std::function function = [obj, slot, args...] () + { + ( *obj.*slot )( args... ); + }; + newProgressOperationImpl( job, text, obj, obj ? function : nullptr, type ); + } /** * Informs the user about the progress of a network request. * At the very least, the user is notified about the start and end of the request. * * @param reply The network reply object whose progress should be monitored * @param text An additional text that will be part of the notification * @param obj The object that will be called if the user cancels the network request. If not set, the progress will not be cancellable * @param slot The slot on the given object that will be called if the user cancels the network request. No slot will be called if not set. * The signal will be emitted from the GUI thread. The receiver may not make assumptions about the sender * @param type The Qt connection type to use for the connection to the receiving slot. Defaults to Qt::AutoConnection */ - virtual void newProgressOperation( QNetworkReply *reply, const QString &text, QObject *obj = 0, const char *slot = 0, Qt::ConnectionType type = Qt::AutoConnection ) = 0; + template + void newProgressOperation( QNetworkReply *reply, const QString &text, Object *obj = nullptr, Func slot = nullptr, Qt::ConnectionType type = Qt::AutoConnection, FuncArgs... args ) + { + std::function function = [obj, slot, args...] () + { + ( *obj.*slot )( args... ); + }; + newProgressOperationImpl( reply, text, obj, obj ? function : nullptr, type ); + } /** * Informs the user about the progress of a generic QObject * - * @param sender The object sending the required signals. This sender must emit singals + * @param sender The object sending the required signals. This sender must emit signals * incrementProgress() and endProgressOperation() and optionally totalSteps(). * @param text An additional text that will be part of the notification * @param maximum The maximum value of the progress operation * @param obj The object that will be called if the user cancels the network request. If not * set, the progress will not be cancellable * @param slot The slot on the given object that will be called if the user cancels the * network request. No slot will be called if not set. * The signal will be emitted from the GUI thread. The receiver may not make assumptions * about the sender * @param type The Qt connection type to use for the connection to the receiving slot. * Defaults to Qt::AutoConnection */ - virtual void newProgressOperation( QObject *sender, const QString &text, int maximum = 100, - QObject *obj = 0, const char *slot = 0, - Qt::ConnectionType type = Qt::AutoConnection ) = 0; + template + typename std::enable_if::value && !std::is_convertible::value && std::is_convertible::value>::type + newProgressOperation( Sender *sender, const QString &text, int maximum = 100, Object *obj = nullptr, Func slot = nullptr, Qt::ConnectionType type = Qt::AutoConnection, FuncArgs... args ) + { + auto increment = QMetaMethod::fromSignal( &Sender::incrementProgress ); + auto end = QMetaMethod::fromSignal( &Sender::endProgressOperation ); + + std::function function = [obj, slot, args...] () + { + ( *obj.*slot )( args... ); + }; + newProgressOperationImpl( sender, increment, end, text, maximum, obj, obj ? function : nullptr, type ); + } /** * Sends a notification to the user. * This method will send a notification containing the given text to the user. * * @param text The text that the notification will contain */ virtual void shortMessage( const QString &text ) = 0; /** * Send a notification to the user with an additional context. * A notification will be send to the user containing the given text. Additionally, it will convey the context given by @p type. * @param text The text that the notification will contain * @param type The context of the notification */ virtual void longMessage( const QString &text, MessageType type = Information ) = 0; + + virtual void newProgressOperationImpl( KJob *job, const QString &text, QObject *context, const std::function &function, Qt::ConnectionType type ) = 0; + virtual void newProgressOperationImpl( QNetworkReply *reply, const QString &text, QObject *context, const std::function &function, Qt::ConnectionType type ) = 0; + virtual void newProgressOperationImpl( QObject *sender, const QMetaMethod &increment, const QMetaMethod &end, const QString &text, + int maximum, QObject *context, const std::function &function, Qt::ConnectionType type ) = 0; }; } #endif diff --git a/src/core/support/Amarok.cpp b/src/core/support/Amarok.cpp index 46b7e555b9..9905fe1f77 100644 --- a/src/core/support/Amarok.cpp +++ b/src/core/support/Amarok.cpp @@ -1,451 +1,454 @@ /**************************************************************************************** * 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 "core/support/Amarok.h" #include "core/meta/Meta.h" #include "core/meta/support/MetaUtility.h" #include "core/capabilities/SourceInfoCapability.h" #include "core/playlists/PlaylistFormat.h" #include #include #include #include #include #include #include #include #include QPointer Amarok::actionCollectionObject; QMutex Amarok::globalDirsMutex; namespace Amarok { // TODO: sometimes we have a playcount but no valid datetime. // in such a case we should maybe display "Unknown" and not "Never" QString verboseTimeSince( const QDateTime &datetime ) { if( datetime.isNull() || !datetime.toTime_t() ) return i18nc( "The amount of time since last played", "Never" ); const QDateTime now = QDateTime::currentDateTime(); const int datediff = datetime.daysTo( now ); // HACK: Fix 203522. Arithmetic overflow? // Getting weird values from Plasma::DataEngine (LAST_PLAYED field). if( datediff < 0 ) return i18nc( "When this track was last played", "Unknown" ); if( datediff >= 6*7 /*six weeks*/ ) { // return absolute month/year QString month_year = datetime.date().toString("MM yyyy"); return i18nc( "monthname year", "%1", month_year ); } //TODO "last week" = maybe within 7 days, but prolly before last Sunday if( datediff >= 7 ) // return difference in weeks return i18np( "One week ago", "%1 weeks ago", (datediff+3)/7 ); const int timediff = datetime.secsTo( now ); if( timediff >= 24*60*60 /*24 hours*/ ) // return difference in days return datediff == 1 ? i18n( "Yesterday" ) : i18np( "One day ago", "%1 days ago", (timediff+12*60*60)/(24*60*60) ); if( timediff >= 90*60 /*90 minutes*/ ) // return difference in hours return i18np( "One hour ago", "%1 hours ago", (timediff+30*60)/(60*60) ); //TODO are we too specific here? Be more fuzzy? ie, use units of 5 minutes, or "Recently" if( timediff >= 0 ) // return difference in minutes return timediff/60 ? i18np( "One minute ago", "%1 minutes ago", (timediff+30)/60 ) : i18n( "Within the last minute" ); return i18n( "The future" ); } QString verboseTimeSince( uint time_t ) { if( !time_t ) return i18nc( "The amount of time since last played", "Never" ); QDateTime dt; dt.setTime_t( time_t ); return verboseTimeSince( dt ); } QString conciseTimeSince( uint time_t ) { if( !time_t ) return i18nc( "The amount of time since last played", "0" ); QDateTime datetime; datetime.setTime_t( time_t ); const QDateTime now = QDateTime::currentDateTime(); const int datediff = datetime.daysTo( now ); if( datediff >= 6*7 /*six weeks*/ ) { // return difference in months return i18nc( "number of months ago", "%1M", datediff/7/4 ); } if( datediff >= 7 ) // return difference in weeks return i18nc( "w for weeks", "%1w", (datediff+3)/7 ); if( datediff == -1 ) return i18nc( "When this track was last played", "Tomorrow" ); const int timediff = datetime.secsTo( now ); if( timediff >= 24*60*60 /*24 hours*/ ) // return difference in days // xgettext: no-c-format return i18nc( "d for days", "%1d", (timediff+12*60*60)/(24*60*60) ); if( timediff >= 90*60 /*90 minutes*/ ) // return difference in hours return i18nc( "h for hours", "%1h", (timediff+30*60)/(60*60) ); //TODO are we too specific here? Be more fuzzy? ie, use units of 5 minutes, or "Recently" if( timediff >= 60 ) // return difference in minutes return QString("%1'").arg( ( timediff + 30 )/60 ); if( timediff >= 0 ) // return difference in seconds return QString("%1\"").arg( ( timediff + 1 )/60 ); return i18n( "0" ); } void manipulateThe( QString &str, bool reverse ) { if( reverse ) { if( !str.startsWith( "the ", Qt::CaseInsensitive ) ) return; QString begin = str.left( 3 ); str = str.append( ", %1" ).arg( begin ); str = str.mid( 4 ); return; } if( !str.endsWith( ", the", Qt::CaseInsensitive ) ) return; QString end = str.right( 3 ); str = str.prepend( "%1 " ).arg( end ); uint newLen = str.length() - end.length() - 2; str.truncate( newLen ); } QString generatePlaylistName( const Meta::TrackList tracks ) { QString datePart = QLocale::system().toString( QDateTime::currentDateTime(), QLocale::ShortFormat ); if( tracks.count() == 0 ) { return i18nc( "A saved playlist with the current time (KLocalizedString::Shortdate) added between \ the parentheses", "Empty Playlist (%1)", datePart ); } bool singleArtist = true; bool singleAlbum = true; Meta::ArtistPtr artist = tracks.first()->artist(); Meta::AlbumPtr album = tracks.first()->album(); QString artistPart; QString albumPart; foreach( const Meta::TrackPtr track, tracks ) { if( artist != track->artist() ) singleArtist = false; if( album != track->album() ) singleAlbum = false; if ( !singleArtist && !singleAlbum ) break; } if( ( !singleArtist && !singleAlbum ) || ( !artist && !album ) ) return i18nc( "A saved playlist with the current time (KLocalizedString::Shortdate) added between \ the parentheses", "Various Tracks (%1)", datePart ); if( singleArtist ) { if( artist ) artistPart = artist->prettyName(); else artistPart = i18n( "Unknown Artist(s)" ); } else if( album && album->hasAlbumArtist() && singleAlbum ) { artistPart = album->albumArtist()->prettyName(); } else { artistPart = i18n( "Various Artists" ); } if( singleAlbum ) { if( album ) albumPart = album->prettyName(); else albumPart = i18n( "Unknown Album(s)" ); } else { albumPart = i18n( "Various Albums" ); } return i18nc( "A saved playlist titled - ", "%1 - %2", artistPart, albumPart ); } KActionCollection* actionCollection() // TODO: constify? { if( !actionCollectionObject ) { actionCollectionObject = new KActionCollection( qApp ); actionCollectionObject->setObjectName( "Amarok-KActionCollection" ); } return actionCollectionObject.data(); } KConfigGroup config( const QString &group ) { //Slightly more useful config() that allows setting the group simultaneously return KSharedConfig::openConfig()->group( group ); } namespace ColorScheme { QColor Base; QColor Text; QColor Background; QColor Foreground; QColor AltBase; } OverrideCursor::OverrideCursor( Qt::CursorShape cursor ) { QApplication::setOverrideCursor( cursor == Qt::WaitCursor ? Qt::WaitCursor : Qt::BusyCursor ); } OverrideCursor::~OverrideCursor() { QApplication::restoreOverrideCursor(); } QString saveLocation( const QString &directory ) { globalDirsMutex.lock(); QString result = QStandardPaths::writableLocation( QStandardPaths::AppDataLocation ) + QDir::separator() + directory; if( !result.endsWith( QDir::separator() ) ) result.append( QDir::separator() ); QDir dir( result ); if( !dir.exists() ) dir.mkpath( QStringLiteral( "." ) ); globalDirsMutex.unlock(); return result; } QString defaultPlaylistPath() { return Amarok::saveLocation() + QLatin1String("current.xspf"); } QString cleanPath( const QString &path ) { /* Unicode uses combining characters to form accented versions of other characters. * (Exception: Latin-1 table for compatibility with ASCII.) * Those can be found in the Unicode tables listed at: * http://en.wikipedia.org/w/index.php?title=Combining_character&oldid=255990982 * Removing those characters removes accents. :) */ QString result = path; // German umlauts result.replace( QChar(0x00e4), "ae" ).replace( QChar(0x00c4), "Ae" ); result.replace( QChar(0x00f6), "oe" ).replace( QChar(0x00d6), "Oe" ); result.replace( QChar(0x00fc), "ue" ).replace( QChar(0x00dc), "Ue" ); result.replace( QChar(0x00df), "ss" ); // other special cases result.replace( QChar(0x00C6), "AE" ); result.replace( QChar(0x00E6), "ae" ); result.replace( QChar(0x00D8), "OE" ); result.replace( QChar(0x00F8), "oe" ); // normalize in a form where accents are separate characters result = result.normalized( QString::NormalizationForm_D ); // remove accents from table "Combining Diacritical Marks" for( int i = 0x0300; i <= 0x036F; i++ ) { result.remove( QChar( i ) ); } return result; } QString asciiPath( const QString &path ) { QString result = path; for( int i = 0; i < result.length(); i++ ) { QChar c = result[ i ]; if( c > QChar(0x7f) || c == QChar(0) ) { c = '_'; } result[ i ] = c; } return result; } QString vfatPath( const QString &path, PathSeparatorBehaviour behaviour ) { + if( path.isEmpty() ) + return QString(); + QString s = path; QChar separator = ( behaviour == AutoBehaviour ) ? QDir::separator() : ( behaviour == UnixBehaviour ) ? '/' : '\\'; if( behaviour == UnixBehaviour ) // we are on *nix, \ is a valid character in file or directory names, NOT the dir separator s.replace( '\\', '_' ); else s.replace( '/', '_' ); // on windows we have to replace / instead int start = 0; #ifdef Q_OS_WIN // exclude the leading "C:/" from special character replecement in the loop below // bug 279560, bug 302251 if( QDir::isAbsolutePath( s ) ) start = 3; #endif for( int i = start; i < s.length(); i++ ) { QChar c = s[ i ]; if( c < QChar(0x20) || c == QChar(0x7F) // 0x7F = 127 = DEL control character || c=='*' || c=='?' || c=='<' || c=='>' || c=='|' || c=='"' || c==':' ) c = '_'; else if( c == '[' ) c = '('; else if ( c == ']' ) c = ')'; s[ i ] = c; } /* beware of reserved device names */ uint len = s.length(); if( len == 3 || (len > 3 && s[3] == '.') ) { QString l = s.left(3).toLower(); if( l=="aux" || l=="con" || l=="nul" || l=="prn" ) s = '_' + s; } else if( len == 4 || (len > 4 && s[4] == '.') ) { QString l = s.left(3).toLower(); QString d = s.mid(3,1); if( (l=="com" || l=="lpt") && (d=="0" || d=="1" || d=="2" || d=="3" || d=="4" || d=="5" || d=="6" || d=="7" || d=="8" || d=="9") ) s = '_' + s; } // "clock$" is only allowed WITH extension, according to: // http://en.wikipedia.org/w/index.php?title=Filename&oldid=303934888#Comparison_of_file_name_limitations if( QString::compare( s, "clock$", Qt::CaseInsensitive ) == 0 ) s = '_' + s; /* max path length of Windows API */ s = s.left(255); /* whitespace or dot at the end of folder/file names or extensions are bad */ len = s.length(); if( s.at(len - 1) == ' ' || s.at(len - 1) == '.' ) s[len - 1] = '_'; for( int i = 1; i < s.length(); i++ ) // correct trailing whitespace in folder names { if( s.at(i) == separator && s.at(i - 1) == ' ' ) s[i - 1] = '_'; } for( int i = 1; i < s.length(); i++ ) // correct trailing dot in folder names, excluding . and .. { if( s.at(i) == separator && s.at(i - 1) == '.' && !( i == 1 // ./any || ( i == 2 && s.at(i - 2) == '.' ) // ../any || ( i >= 2 && s.at(i - 2) == separator ) // any/./any || ( i >= 3 && s.at(i - 3) == separator && s.at(i - 2) == '.' ) // any/../any ) ) s[i - 1] = '_'; } /* correct trailing spaces in file name itself, not needed for dots */ int extensionIndex = s.lastIndexOf( '.' ); if( ( s.length() > 1 ) && ( extensionIndex > 0 ) ) if( s.at(extensionIndex - 1) == ' ' ) s[extensionIndex - 1] = '_'; return s; } QPixmap semiTransparentLogo( int dim ) { QPixmap logo; #define AMAROK_LOGO_CACHE_KEY QLatin1String("AmarokSemiTransparentLogo")+QString::number(dim) if( !QPixmapCache::find( AMAROK_LOGO_CACHE_KEY, &logo ) ) { QImage amarokIcon = QIcon::fromTheme( QLatin1String("amarok") ).pixmap( dim, dim ).toImage(); amarokIcon = amarokIcon.convertToFormat( QImage::Format_ARGB32 ); QRgb *data = reinterpret_cast( amarokIcon.bits() ); QRgb *end = data + amarokIcon.byteCount() / 4; while(data != end) { unsigned char gray = qGray(*data); *data = qRgba(gray, gray, gray, 127); ++data; } logo = QPixmap::fromImage( amarokIcon ); QPixmapCache::insert( AMAROK_LOGO_CACHE_KEY, logo ); } #undef AMAROK_LOGO_CACHE_KEY return logo; } } // End namespace Amarok diff --git a/src/core/support/SmartPointerList.cpp b/src/core/support/SmartPointerList.cpp deleted file mode 100644 index c6fe6c043a..0000000000 --- a/src/core/support/SmartPointerList.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/**************************************************************************************** - * Copyright (c) 2009 Nikolaj Hald Nielsen * - * Copyright (c) 2009 Mark Kretschmann * - * Copyright (c) 2009 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 . * - ****************************************************************************************/ - -//A file to make automoc happy - -#include "core/support/SmartPointerList.h" - diff --git a/src/core/support/SmartPointerList.h b/src/core/support/SmartPointerList.h deleted file mode 100644 index d8edbbaffb..0000000000 --- a/src/core/support/SmartPointerList.h +++ /dev/null @@ -1,211 +0,0 @@ -/**************************************************************************************** - * Copyright (c) 2009 Mark Kretschmann * - * Copyright (c) 2009 Ian Monroe * - * Copyright (c) 2009 Max Howell * - * * - * This program is free software; you can redistribute it and/or modify it under * - * the 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_SMART_POINTER_LIST_H -#define AMAROK_SMART_POINTER_LIST_H - -#include "core/amarokcore_export.h" - -#include //baseclass -#include //baseclass - -#ifdef Q_CC_MSVC -class SmartPointerListDaddy : public QObject -#else -class AMAROK_CORE_EXPORT SmartPointerListDaddy : public QObject -#endif -{ - Q_OBJECT - QList& m_list; - -public: -#ifdef Q_CC_MSVC - AMAROK_CORE_EXPORT explicit SmartPointerListDaddy( QList* list ) : m_list( *list ) -#else - explicit SmartPointerListDaddy( QList* list ) : m_list( *list ) -#endif - {} - -private Q_SLOTS: - void onDestroyed() - { - m_list.removeAll( sender() ); - } -}; - -/** QList has no virtual functions, so we inherit privately and define the - * interface exactly to ensure users can't write code that breaks the - * class's internal behaviour. - * - * I deliberately didn't define clear. I worried people would assume it - * deleted the pointers. Or assume it didn't. I didn't expose a few other - * functions for that reason. - * - * non-const iterator functions are not exposed as they access the QList - * baseclass, and then the Daddy wouldn't be watching newly inserted items. - * - * --mxcl - * Exposed clear. This class doesn't have a QPtrList autodelete functionality - * ever, so if people think that, they're really confused! -- Ian Monroe - * - */ -template class SmartPointerList : private QList -{ - class SmartPointerListDaddy* m_daddy; - -public: - SmartPointerList() : m_daddy( new SmartPointerListDaddy( (QList*)this ) ) - {} - - ~SmartPointerList() - { - delete m_daddy; - } - - SmartPointerList( const SmartPointerList& that ) - : QList() - , m_daddy( new SmartPointerListDaddy( (QList*)this ) ) - { - QListIterator i( that ); - while (i.hasNext()) - append( i.next() ); - } - - SmartPointerList& operator=( const SmartPointerList& that ) - { - QListIterator i( *this); - while (i.hasNext()) - QObject::disconnect( m_daddy, 0, i.next(), 0 ); - - QList::operator=( that ); - - if (this != &that) { - QListIterator i( that ); - while (i.hasNext()) - m_daddy->connect( i.next(), SIGNAL(destroyed()), SLOT(onDestroyed()) ); - } - - return *this; - } - - // keep same function names as Qt - void append( T* o ) - { - m_daddy->connect( o, SIGNAL(destroyed()), SLOT(onDestroyed()) ); - QList::append( o ); - } - - void prepend( T* o ) - { - m_daddy->connect( o, SIGNAL(destroyed()), SLOT(onDestroyed()) ); - QList::prepend( o ); - } - - SmartPointerList& operator+=( T* o ) - { - append( o ); - return *this; - } - - SmartPointerList& operator<<( T* o ) - { - return operator+=( o ); - } - - SmartPointerList operator+( const SmartPointerList that ) - { - SmartPointerList copy = *this; - QListIterator i( that ); - while (i.hasNext()) - copy.append( i.next() ); - return copy; - } - - SmartPointerList& operator+=( const SmartPointerList that ) - { - QListIterator i( that ); - while (i.hasNext()) - append( i.next() ); - return *this; - } - - void push_back( T* o ) - { - append( o ); - } - - void push_front( T* o ) - { - prepend( o ); - } - - void replace( int i, T* o ) - { - QList::replace( i, o ); - m_daddy->connect( o, SIGNAL(destroyed()), SLOT(onDestroyed()) ); - } - - /** this is a "safe" class. We always bounds check */ - inline T* operator[]( int index ) const { return QList::value( index ); } - inline T* at( int index ) const { return QList::value( index ); } - - // make public safe functions again - using QList::back; - using QList::constBegin; - using QList::constEnd; - using typename QList::const_iterator; - using QList::contains; - using QList::count; - using QList::empty; - using QList::erase; - using QList::first; - using QList::front; - using QList::indexOf; - using QList::insert; - using QList::isEmpty; - using QList::last; - using QList::lastIndexOf; - using QList::mid; - using QList::move; - using QList::pop_back; - using QList::pop_front; - using QList::size; - using QList::swap; - using QList::value; - using QList::operator!=; - using QList::operator==; - - // can't use using directive here since we only want the const versions - typename QList::const_iterator begin() const { return QList::constBegin(); } - typename QList::const_iterator end() const { return QList::constEnd(); } - - // it can lead to poor performance situations if we don't disconnect - // but I think it's not worth making this class more complicated for such - // an edge case - using QList::clear; - using QList::removeAll; - using QList::removeAt; - using QList::removeFirst; - using QList::removeLast; - using QList::removeOne; - using QList::takeAt; - using QList::takeFirst; - using QList::takeLast; -}; - -#endif //HEADER_GUARD diff --git a/src/dynamic/BiasedPlaylist.cpp b/src/dynamic/BiasedPlaylist.cpp index 26cf7d6f39..4c50361bab 100644 --- a/src/dynamic/BiasedPlaylist.cpp +++ b/src/dynamic/BiasedPlaylist.cpp @@ -1,219 +1,219 @@ /**************************************************************************************** * Copyright (c) 2008 Daniel Caleb Jones * * 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) 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 "BiasedPlaylist" #include "BiasedPlaylist.h" #include "App.h" #include "amarokconfig.h" #include "core/collections/Collection.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/CollectionManager.h" #include "dynamic/BiasSolver.h" #include "dynamic/BiasFactory.h" #include "dynamic/DynamicModel.h" #include "playlist/PlaylistModelStack.h" // for The::playlist #include #include #include Dynamic::BiasedPlaylist::BiasedPlaylist( QObject *parent ) : DynamicPlaylist( parent ) , m_bias( 0 ) , m_solver( 0 ) { m_title = i18nc( "Title for a default dynamic playlist. The default playlist only returns random tracks.", "Random" ); BiasPtr biasPtr( BiasPtr( new Dynamic::RandomBias() ) ); biasReplaced( BiasPtr(), biasPtr ); } Dynamic::BiasedPlaylist::BiasedPlaylist( QXmlStreamReader *reader, QObject *parent ) : DynamicPlaylist( parent ) , m_bias( 0 ) , m_solver( 0 ) { while (!reader->atEnd()) { reader->readNext(); if( reader->isStartElement() ) { QStringRef name = reader->name(); if( name == "title" ) m_title = reader->readElementText(QXmlStreamReader::SkipChildElements); else { BiasPtr biasPtr( Dynamic::BiasFactory::fromXml( reader ) ); if( biasPtr ) { biasReplaced( BiasPtr(), biasPtr ); } else { debug()<<"Unexpected xml start element"<name()<<"in input"; reader->skipCurrentElement(); } } } else if( reader->isEndElement() ) { break; } } } Dynamic::BiasedPlaylist::~BiasedPlaylist() { requestAbort(); } void Dynamic::BiasedPlaylist::toXml( QXmlStreamWriter *writer ) const { writer->writeTextElement( "title", m_title ); writer->writeStartElement( m_bias->name() ); m_bias->toXml( writer ); writer->writeEndElement(); } void Dynamic::BiasedPlaylist::requestAbort() { DEBUG_BLOCK if( m_solver ) { m_solver->setAutoDelete( true ); m_solver->requestAbort(); m_solver = 0; } } void Dynamic::BiasedPlaylist::startSolver( int numRequested ) { DEBUG_BLOCK debug() << "BiasedPlaylist in:" << QThread::currentThreadId(); if( !m_solver ) { debug() << "assigning new m_solver"; m_solver = new BiasSolver( numRequested, m_bias, getContext() ); connect( m_solver, &BiasSolver::done, this, &BiasedPlaylist::solverFinished ); Amarok::Components::logger()->newProgressOperation( m_solver, i18n( "Generating playlist..." ), 100, - this, SLOT(requestAbort()) ); + this, &BiasedPlaylist::requestAbort ); ThreadWeaver::Queue::instance()->enqueue( QSharedPointer(m_solver) ); debug() << "called prepareToRun"; } else debug() << "solver already running!"; } void Dynamic::BiasedPlaylist::biasChanged() { emit changed( this ); bool inModel = DynamicModel::instance()->index( this ).isValid(); if( inModel ) DynamicModel::instance()->biasChanged( m_bias ); } void Dynamic::BiasedPlaylist::biasReplaced( Dynamic::BiasPtr oldBias, Dynamic::BiasPtr newBias ) { if( oldBias && !newBias ) // don't move the last bias away from this playlist without replacement return; bool inModel = DynamicModel::instance()->index( this ).isValid(); if( m_bias ) { disconnect( m_bias.data(), 0, this, 0 ); if( inModel ) Dynamic::DynamicModel::instance()->beginRemoveBias( this ); m_bias = 0; if( inModel ) Dynamic::DynamicModel::instance()->endRemoveBias(); } if( inModel ) Dynamic::DynamicModel::instance()->beginInsertBias( this ); m_bias = newBias; if( inModel ) Dynamic::DynamicModel::instance()->endInsertBias(); connect( m_bias.data(), &AbstractBias::changed, this, &BiasedPlaylist::biasChanged ); connect( m_bias.data(), &AbstractBias::replaced, this, &BiasedPlaylist::biasReplaced ); if( oldBias ) // don't emit a changed during construction biasChanged(); } void Dynamic::BiasedPlaylist::requestTracks( int n ) { if( n > 0 ) startSolver( n + 1 ); // we request an additional track so that we don't end up in a position that e.g. does have no "similar" track. } Dynamic::BiasPtr Dynamic::BiasedPlaylist::bias() const { return m_bias; } void Dynamic::BiasedPlaylist::solverFinished() { DEBUG_BLOCK if( m_solver != sender() ) return; // maybe an old solver... aborted solvers should autodelete Meta::TrackList list = m_solver->solution(); if( list.count() > 0 ) { // remove the additional requested track if( list.count() > 1 ) list.removeLast(); emit tracksReady( list ); } m_solver->deleteLater(); m_solver = 0; } Meta::TrackList Dynamic::BiasedPlaylist::getContext() { Meta::TrackList context = The::playlist()->tracks(); return context; } diff --git a/src/playlistgenerator/Preset.cpp b/src/playlistgenerator/Preset.cpp index 6c2177b52a..24d11c9fed 100644 --- a/src/playlistgenerator/Preset.cpp +++ b/src/playlistgenerator/Preset.cpp @@ -1,161 +1,161 @@ /**************************************************************************************** * Copyright (c) 2008-2010 Soren Harward * * * * This program is free software; you can redistribute it and/or modify it under * * the 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 "APG::Preset" #include "Preset.h" #include "ConstraintNode.h" #include "ConstraintFactory.h" #include "ConstraintSolver.h" #include "constraints/TrackSpreader.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/CollectionManager.h" #include "playlist/PlaylistController.h" #include #include #include APG::PresetPtr APG::Preset::createFromXml( QDomElement& xmlelem ) { DEBUG_BLOCK if ( xmlelem.isNull() ) { PresetPtr t( new Preset( i18n("New playlist preset") ) ); return t; } else { PresetPtr t( new Preset( i18n("Unnamed playlist preset"), xmlelem ) ); return t; } } APG::PresetPtr APG::Preset::createNew() { DEBUG_BLOCK PresetPtr t( new Preset( i18n("New playlist preset") ) ); return t; } APG::Preset::Preset( const QString& title, QDomElement& xmlelem ) : m_title( title ) , m_constraintTreeRoot( 0 ) { if ( xmlelem.hasAttribute( "title" ) ) { m_title = xmlelem.attribute( "title" ); } else { m_title = i18n("Unnamed playlist preset"); } for ( int i = 0; i < xmlelem.childNodes().count(); i++ ) { QDomElement childXmlElem = xmlelem.childNodes().item( i ).toElement(); if ( !childXmlElem.isNull() ) { if ( childXmlElem.tagName() == "constrainttree" ) { m_constraintTreeRoot = ConstraintFactory::instance()->createGroup( childXmlElem, 0 ); } else { error() << "unknown child: " << childXmlElem.nodeName(); } } } if ( !m_constraintTreeRoot ) { m_constraintTreeRoot = ConstraintFactory::instance()->createGroup( 0 ); } } APG::Preset::Preset( const QString& title ) : m_title( title ) { m_constraintTreeRoot = ConstraintFactory::instance()->createGroup( 0 ); } APG::Preset::~Preset() { delete m_constraintTreeRoot; } QDomElement* APG::Preset::toXml( QDomDocument& xmldoc ) const { QDomElement e = xmldoc.createElement( "generatorpreset" ); e.setAttribute( "title", m_title ); m_constraintTreeRoot->toXml( xmldoc, e ); return new QDomElement( e ); } void APG::Preset::generate( int q ) { ConstraintSolver* solver = new ConstraintSolver( m_constraintTreeRoot, q ); connect( solver, &ConstraintSolver::readyToRun, this, &Preset::queueSolver ); } void APG::Preset::queueSolver() { /* Workaround for a design quirk of Weavers. A Weaver will not * continuously poll queued but previously unrunnable jobs to see if they * are are runnable (and won't start them if they are), so what tends to * happen is that if a job is queued before it's ready to run, it fails the * canBeExecuted() check, and sits in the queue until something wakes up * the Weaver (eg, queueing another job). Eventually, you get a pileup of * obsolete jobs in the queue, and those that do run properly return * obsolete results. So to avoid that problem, we avoid queueing the job * until it's ready to run, and then the Weaver will start running it * pretty much immediately. -- sth */ emit lock( true ); ConstraintSolver* s = static_cast( sender() ); - Amarok::Components::logger()->newProgressOperation( s, i18n("Generating a new playlist"), s->iterationCount(), s, SLOT(requestAbort()), Qt::QueuedConnection ); + Amarok::Components::logger()->newProgressOperation( s, i18n("Generating a new playlist"), s->iterationCount(), s, &ConstraintSolver::requestAbort, Qt::QueuedConnection ); connect( s, &APG::ConstraintSolver::done, this, &Preset::solverFinished, Qt::QueuedConnection ); m_constraintTreeRoot->addChild( ConstraintTypes::TrackSpreader::createNew( m_constraintTreeRoot ), 0 ); // private mandatory constraint ThreadWeaver::Queue::instance()->enqueue( QSharedPointer(s) ); } void APG::Preset::solverFinished( ThreadWeaver::JobPointer job ) { m_constraintTreeRoot->removeChild( 0 ); // remove the TrackSpreader ConstraintSolver* solver = static_cast( job.data() ); if ( job->success() ) { debug() << "Solver" << solver->serial() << "finished successfully"; if ( !solver->satisfied() ) { Amarok::Components::logger()->longMessage( i18n("The playlist generator created a playlist which does not meet all " "of your constraints. If you are not satisfied with the results, " "try loosening or removing some constraints and then generating a " "new playlist.") ); } The::playlistController()->insertOptioned( solver->getSolution(), Playlist::OnReplacePlaylistAction ); } else { debug() << "Ignoring results from aborted Solver" << solver->serial(); } emit lock( false ); } diff --git a/src/scanner/AbstractScanResultProcessor.cpp b/src/scanner/AbstractScanResultProcessor.cpp index cb94849a46..a9b71a75c0 100644 --- a/src/scanner/AbstractScanResultProcessor.cpp +++ b/src/scanner/AbstractScanResultProcessor.cpp @@ -1,310 +1,310 @@ /**************************************************************************************** * Copyright (c) 2007 Maximilian Kossick * * Copyright (c) 2008 Seb Ruiz * * Copyright (c) 2009-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 "AbstractScanResultProcessor" #include "AbstractScanResultProcessor.h" #include "collectionscanner/Album.h" #include "collectionscanner/Directory.h" #include "collectionscanner/Playlist.h" #include "collectionscanner/Track.h" #include "core/interfaces/Logger.h" #include "core/support/Debug.h" #include "core/support/Components.h" #include "core-impl/collections/support/ArtistHelper.h" #include "playlistmanager/PlaylistManager.h" #include "scanner/GenericScanManager.h" AbstractScanResultProcessor::AbstractScanResultProcessor( GenericScanManager* manager, QObject* parent ) : QObject( parent ) , m_manager( manager ) , m_type( GenericScanManager::PartialUpdateScan ) { connect( manager, &GenericScanManager::started, this, &AbstractScanResultProcessor::scanStarted ); connect( manager, &GenericScanManager::directoryCount, this, &AbstractScanResultProcessor::scanDirectoryCount ); connect( manager, &GenericScanManager::directoryScanned, this, &AbstractScanResultProcessor::scanDirectoryScanned ); connect( manager, &GenericScanManager::succeeded, this, &AbstractScanResultProcessor::scanSucceeded ); connect( manager, &GenericScanManager::failed, this, &AbstractScanResultProcessor::scanFailed ); } AbstractScanResultProcessor::~AbstractScanResultProcessor() { cleanupMembers(); } void AbstractScanResultProcessor::scanStarted( GenericScanManager::ScanType type ) { DEBUG_BLOCK; m_type = type; // -- show the progress operation for the job if( Amarok::Components::logger() ) Amarok::Components::logger()->newProgressOperation( this, i18n( "Scanning music" ), 100, this, - SLOT(abort()) ); + &AbstractScanResultProcessor::abort ); } void AbstractScanResultProcessor::scanDirectoryCount( int count ) { // message( i18np("Found one directory", "Found %1 directories", count ) ); debug() << "got" << count << "directories"; emit totalSteps( count * 2 ); } void AbstractScanResultProcessor::scanDirectoryScanned( QSharedPointer dir ) { m_directories.append( dir ); emit incrementProgress(); } void AbstractScanResultProcessor::scanSucceeded() { DEBUG_BLOCK; // the default for albums with several artists is that it's a compilation // however, some album names are unlikely to be a compilation static QStringList nonCompilationAlbumNames; if( nonCompilationAlbumNames.isEmpty() ) { nonCompilationAlbumNames << "" // don't throw together albums without name. At least not here << "Best Of" << "Anthology" << "Hit collection" << "Greatest Hits" << "All Time Greatest Hits" << "Live"; } // -- commit the directories foreach( QSharedPointer dir, m_directories ) { commitDirectory( dir ); // -- sort the tracks into albums QSet dirAlbums; QSet dirAlbumNames; QList tracks = dir->tracks(); // check what album names we have foreach( CollectionScanner::Track* track, dir->tracks() ) { if( !track->album().isEmpty() ) dirAlbumNames.insert( track->album() ); } // use the directory name as album name QString fallbackAlbumName = ( dirAlbumNames.isEmpty() ? QDir( dir->path() ).dirName() : QString() ); foreach( CollectionScanner::Track* track, dir->tracks() ) { CollectionScanner::Album *album = sortTrack( track, fallbackAlbumName ); dirAlbums.insert( album ); dirAlbumNames.insert( track->album() ); } // if all the tracks from this directory end up in one album // (or they have at least the same name) then it's likely that an image // from this directory could be a cover if( dirAlbumNames.count() == 1 ) (*dirAlbums.begin())->setCovers( dir->covers() ); } // --- add all albums QList keys = m_albumNames.uniqueKeys(); emit totalSteps( m_directories.count() + keys.count() ); // update progress bar foreach( const QString &key, keys ) { // --- commit the albums as compilation or normal album QList albums = m_albumNames.values( key ); // if we have multiple albums with the same name, check if it // might be a compilation for( int i = albums.count() - 1; i >= 0; --i ) { CollectionScanner::Album *album = albums.at( i ); // commit all albums with a track with the noCompilation flag if( album->isNoCompilation() || nonCompilationAlbumNames.contains( album->name(), Qt::CaseInsensitive ) ) commitAlbum( albums.takeAt( i ) ); } // only one album left. (that either means all have the same album artist tag // or none has one. In the last case we try to guess an album artist) if( albums.count() == 1 ) { CollectionScanner::Album *album = albums.takeFirst(); // look if all the tracks have the same (guessed) artist. // It's no compilation. bool isCompilation = false; bool firstTrack = true; QString albumArtist; foreach( CollectionScanner::Track *track, album->tracks() ) { QString trackAlbumArtist = ArtistHelper::bestGuessAlbumArtist( track->albumArtist(), track->artist(), track->genre(), track->composer() ); if( firstTrack ) albumArtist = trackAlbumArtist; firstTrack = false; if( trackAlbumArtist.isEmpty() || track->isCompilation() || albumArtist != trackAlbumArtist ) isCompilation = true; } if( !isCompilation ) album->setArtist( albumArtist ); commitAlbum( album ); } // several albums with different album artist. else if( albums.count() > 1 ) { while( !albums.isEmpty() ) commitAlbum( albums.takeFirst() ); } emit incrementProgress(); } // -- now check if some of the tracks are not longer used and also not moved to another directory foreach( QSharedPointer dir, m_directories ) if( !dir->isSkipped() ) deleteDeletedTracksAndSubdirs( dir ); // -- delete all not-found directories (special handling for PartialUpdateScan): deleteDeletedDirectories(); cleanupMembers(); emit endProgressOperation( this ); } void AbstractScanResultProcessor::scanFailed( const QString& text ) { message( text ); cleanupMembers(); emit endProgressOperation( this ); } void AbstractScanResultProcessor::abort() { m_manager->abort(); } void AbstractScanResultProcessor::commitDirectory( QSharedPointer dir ) { if( dir->path().isEmpty() ) { warning() << "got directory with no path from the scanner, not adding"; return; } // --- add all playlists foreach( const CollectionScanner::Playlist &playlist, dir->playlists() ) commitPlaylist( playlist ); } void AbstractScanResultProcessor::commitPlaylist( const CollectionScanner::Playlist &playlist ) { // debug() << "commitPlaylist on " << playlist->path(); if( The::playlistManager() ) The::playlistManager()->import( QUrl::fromLocalFile( playlist.path() ) ); } /** This will just put the tracks into an album. @param album the name of the target album @returns true if the track was put into an album */ CollectionScanner::Album* AbstractScanResultProcessor::sortTrack( CollectionScanner::Track *track, const QString &dirName ) { QString albumName = track->album(); // we allow albums with empty name and nonepty artist, see bug 272471 QString albumArtist = track->albumArtist(); if( track->isCompilation() ) albumArtist.clear(); // no album artist denotes a compilation if( track->isNoCompilation() && albumArtist.isEmpty() ) albumArtist = ArtistHelper::bestGuessAlbumArtist( track->albumArtist(), track->artist(), track->genre(), track->composer() ); if( albumName.isEmpty() && albumArtist.isEmpty() ) // try harder to set at least one albumName = dirName; AlbumKey key( albumName, albumArtist ); CollectionScanner::Album *album; if( m_albums.contains( key ) ) album = m_albums.value( key ); else { album = new CollectionScanner::Album( albumName, albumArtist ); m_albums.insert( key, album ); m_albumNames.insert( albumName, album ); } album->addTrack( track ); return album; } void AbstractScanResultProcessor::cleanupMembers() { // note: qt had problems with CLEAN_ALL macro QHash::const_iterator i = m_albumNames.constBegin(); while (i != m_albumNames.constEnd()) { delete i.value(); ++i; } m_albumNames.clear(); m_albums.clear(); m_directories.clear(); } diff --git a/src/services/ServicePluginManager.cpp b/src/services/ServicePluginManager.cpp index b74a40de7a..452164cbcc 100644 --- a/src/services/ServicePluginManager.cpp +++ b/src/services/ServicePluginManager.cpp @@ -1,190 +1,190 @@ /**************************************************************************************** * Copyright (c) 2007 Maximilian Kossick * * 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 . * ****************************************************************************************/ #define DEBUG_PREFIX "ServicePluginManager" #include "ServicePluginManager.h" #include "browsers/servicebrowser/ServiceBrowser.h" #include "core/support/Debug.h" #include "services/ServiceBase.h" #include #include #include ServicePluginManager *ServicePluginManager::s_instance = 0; ServicePluginManager * ServicePluginManager::instance() { if( !s_instance ) { s_instance = new ServicePluginManager(); } return s_instance; } void ServicePluginManager::destroy() { if( s_instance ) { delete s_instance; s_instance = 0; } } ServicePluginManager::ServicePluginManager() : QObject() { DEBUG_BLOCK // ensure this object is created in a main thread Q_ASSERT( thread() == QCoreApplication::instance()->thread() ); setObjectName( "ServicePluginManager" ); } ServicePluginManager::~ServicePluginManager() { } void -ServicePluginManager::setFactories( const QList &factories ) +ServicePluginManager::setFactories( const QList > &factories ) { - QSet newFactories = factories.toSet(); - QSet oldFactories = m_factories.toSet(); + QSet > newFactories = factories.toSet(); + QSet > oldFactories = m_factories.toSet(); // remove old factories - foreach( Plugins::PluginFactory* pFactory, oldFactories - newFactories ) + for( const auto &pFactory : oldFactories - newFactories ) { - ServiceFactory *factory = qobject_cast( pFactory ); + auto factory = qobject_cast( pFactory ); if( !factory ) continue; foreach( ServiceBase * service, factory->activeServices() ) ServiceBrowser::instance()->removeCategory( service ); factory->clearActiveServices(); } // create new factories - foreach( Plugins::PluginFactory* pFactory, newFactories - oldFactories ) + for( const auto &pFactory : newFactories - oldFactories ) { - ServiceFactory *factory = qobject_cast( pFactory ); + auto factory = qobject_cast( pFactory ); if( !factory ) continue; - connect( factory, &ServiceFactory::newService, this, &ServicePluginManager::slotNewService ); - connect( factory, &ServiceFactory::removeService, this, &ServicePluginManager::slotRemoveService ); + connect( factory.data(), &ServiceFactory::newService, this, &ServicePluginManager::slotNewService ); + connect( factory.data(), &ServiceFactory::removeService, this, &ServicePluginManager::slotRemoveService ); } m_factories = factories; } void ServicePluginManager::slotNewService( ServiceBase *newService ) { DEBUG_BLOCK debug() << "new service:" << newService->name(); ServiceBrowser::instance()->addCategory( newService ); } void ServicePluginManager::slotRemoveService( ServiceBase *removedService ) { DEBUG_BLOCK debug() << "removed service:" << removedService->name(); ServiceBrowser::instance()->removeCategory( removedService ); } QStringList ServicePluginManager::loadedServices() const { QStringList names; - foreach( Plugins::PluginFactory *pFactory, m_factories ) + for( const auto &pFactory : m_factories ) { - ServiceFactory *factory = qobject_cast( pFactory ); + auto factory = qobject_cast( pFactory ); if( !factory ) continue; foreach( ServiceBase *service, factory->activeServices() ) names << service->name(); } return names; } QStringList ServicePluginManager::loadedServiceNames() const { return ServiceBrowser::instance()->categories().keys(); } QString ServicePluginManager::serviceDescription( const QString & serviceName ) { //get named service if ( !ServiceBrowser::instance()->categories().contains( serviceName ) ) { return i18n( "No service named %1 is currently loaded", serviceName ); } ServiceBase * service = dynamic_cast( ServiceBrowser::instance()->categories().value( serviceName ) ); if ( service == 0 ) return QString(); return service->shortDescription(); } QString ServicePluginManager::serviceMessages( const QString & serviceName ) { //get named service if ( !ServiceBrowser::instance()->categories().contains( serviceName ) ) { return i18n( "No service named %1 is currently loaded", serviceName ); } ServiceBase * service = dynamic_cast( ServiceBrowser::instance()->categories().value( serviceName ) ); if ( service == 0 ) return QString(); return service->messages(); } QString ServicePluginManager::sendMessage( const QString & serviceName, const QString & message ) { //get named service if ( !ServiceBrowser::instance()->categories().contains( serviceName ) ) { return i18n( "No service named %1 is currently loaded", serviceName ); } ServiceBase * service = dynamic_cast( ServiceBrowser::instance()->categories().value( serviceName ) ); if ( service == 0 ) return QString(); return service->sendMessage( message ); } diff --git a/src/services/ServicePluginManager.h b/src/services/ServicePluginManager.h index 40f22f219c..736d2ffad4 100644 --- a/src/services/ServicePluginManager.h +++ b/src/services/ServicePluginManager.h @@ -1,76 +1,76 @@ /**************************************************************************************** * 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 . * ****************************************************************************************/ #ifndef AMAROK_SERVICEPLUGINMANAGER_H #define AMAROK_SERVICEPLUGINMANAGER_H #include #include #include class ServiceBase; class ServiceBrowser; class ServiceFactory; namespace Plugins { class PluginFactory; } /** A class to keep track of available service plugins and load them as needed * * @author */ class ServicePluginManager : public QObject { Q_OBJECT public: static ServicePluginManager *instance(); static void destroy(); /** * Load any services that are configured to be loaded. * Unload any services that have been switched off. */ - void setFactories( const QList &factories ); + void setFactories( const QList > &factories ); public Q_SLOTS: QStringList loadedServices() const; QStringList loadedServiceNames() const; QString serviceDescription( const QString &service ); QString serviceMessages( const QString &service ); QString sendMessage( const QString &service, const QString &message ); private: static ServicePluginManager* s_instance; ServicePluginManager(); ~ServicePluginManager(); Q_DISABLE_COPY( ServicePluginManager ) /** The list of currently set factories. * Note: the PluginManager owns the pointers. */ - QList m_factories; + QList > m_factories; private Q_SLOTS: void slotNewService( ServiceBase *newService); void slotRemoveService( ServiceBase *removedService ); }; #endif //AMAROK_SERVICEPLUGINMANAGER_H diff --git a/src/services/magnatune/MagnatuneAlbumDownloader.cpp b/src/services/magnatune/MagnatuneAlbumDownloader.cpp index c149e510ac..06d550e902 100644 --- a/src/services/magnatune/MagnatuneAlbumDownloader.cpp +++ b/src/services/magnatune/MagnatuneAlbumDownloader.cpp @@ -1,179 +1,179 @@ /**************************************************************************************** * Copyright (c) 2006,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 "MagnatuneAlbumDownloader.h" #include "core/support/Amarok.h" #include "core/support/Components.h" #include "core/support/Debug.h" #include "core/interfaces/Logger.h" #include "MagnatuneMeta.h" #include #include #include MagnatuneAlbumDownloader::MagnatuneAlbumDownloader() : QObject() , m_albumDownloadJob( Q_NULLPTR ) , m_coverDownloadJob( Q_NULLPTR ) , m_currentAlbumFileName() { m_tempDir = new QTemporaryDir(); } MagnatuneAlbumDownloader::~MagnatuneAlbumDownloader() { delete m_tempDir; } void MagnatuneAlbumDownloader::downloadAlbum( MagnatuneDownloadInfo info ) { DEBUG_BLOCK m_currentAlbumInfo = info; QUrl downloadUrl = info.completeDownloadUrl(); m_currentAlbumUnpackLocation = info.unpackLocation(); debug() << "Download: " << downloadUrl.url() << " to: " << m_currentAlbumUnpackLocation; m_currentAlbumFileName = info.albumCode() + ".zip"; debug() << "Using temporary location: " << m_tempDir->path() + '/' + m_currentAlbumFileName; m_albumDownloadJob = KIO::file_copy( downloadUrl, QUrl::fromLocalFile( m_tempDir->path() + '/' + m_currentAlbumFileName ), -1, KIO::Overwrite | KIO::HideProgressInfo ); connect( m_albumDownloadJob, &KJob::result, this, &MagnatuneAlbumDownloader::albumDownloadComplete ); QString msgText; if( !info.albumName().isEmpty() && !info.artistName().isEmpty() ) { msgText = i18n( "Downloading '%1' by %2 from Magnatune.com", info.albumName(), info.artistName() ); } else { msgText = i18n( "Downloading album from Magnatune.com" ); } - Amarok::Components::logger()->newProgressOperation( m_albumDownloadJob, msgText, this, SLOT(albumDownloadAborted()) ); + Amarok::Components::logger()->newProgressOperation( m_albumDownloadJob, msgText, this, &MagnatuneAlbumDownloader::albumDownloadAborted ); } void MagnatuneAlbumDownloader::albumDownloadComplete( KJob * downloadJob ) { DEBUG_BLOCK debug() << "album download complete"; if ( downloadJob->error() ) { //TODO: error handling here return ; } if ( downloadJob != m_albumDownloadJob ) return ; //not the right job, so let's ignore it const QString finalAlbumPath = m_currentAlbumUnpackLocation + '/' + m_currentAlbumInfo.artistName() + '/' + m_currentAlbumInfo.albumName(); //ok, now we have the .zip file downloaded. All we need is to unpack it to the desired location and add it to the collection. KZip kzip( m_tempDir->path() + '/' + m_currentAlbumFileName ); if ( !kzip.open( QIODevice::ReadOnly ) ) { Amarok::Components::logger()->shortMessage( i18n( "Magnatune download seems to have failed. Cannot read zip file" ) ); emit( downloadComplete( false ) ); return; } debug() << m_tempDir->path() + '/' + m_currentAlbumFileName << " opened for decompression"; const KArchiveDirectory * directory = kzip.directory(); Amarok::Components::logger()->shortMessage( i18n( "Uncompressing Magnatune.com download..." ) ); //Is this really blocking with no progress status!? Why is it not a KJob? debug() << "decompressing to " << finalAlbumPath; directory->copyTo( m_currentAlbumUnpackLocation ); debug() << "done!"; //now I really want to add the album cover to the same folder where I just unzipped the album... The //only way of getting the actual location where the album was unpacked is using the artist and album names QString coverUrlString = m_currentAlbumInfo.coverUrl(); QUrl downloadUrl( coverUrlString.replace( "_200.jpg", ".jpg") ); debug() << "Adding cover " << downloadUrl.url() << " to collection at " << finalAlbumPath; m_coverDownloadJob = KIO::file_copy( downloadUrl, QUrl::fromLocalFile( finalAlbumPath + "/cover.jpg" ), -1, KIO::Overwrite | KIO::HideProgressInfo ); connect( m_coverDownloadJob, &KJob::result, this, &MagnatuneAlbumDownloader::coverDownloadComplete ); - Amarok::Components::logger()->newProgressOperation( m_coverDownloadJob, i18n( "Adding album cover to collection" ), this, SLOT(coverAddAborted()) ); + Amarok::Components::logger()->newProgressOperation( m_coverDownloadJob, i18n( "Adding album cover to collection" ), this, &MagnatuneAlbumDownloader::coverAddAborted ); emit( downloadComplete( true ) ); } void MagnatuneAlbumDownloader::coverDownloadComplete(KJob* downloadJob) { DEBUG_BLOCK debug() << "cover download complete"; if ( downloadJob->error() ) { //TODO: error handling here return ; } if ( downloadJob != m_coverDownloadJob ) return ; //not the right job, so let's ignore it //TODO: storing of cover here } void MagnatuneAlbumDownloader::albumDownloadAborted( ) { DEBUG_BLOCK m_albumDownloadJob->kill(); m_albumDownloadJob = Q_NULLPTR; debug() << "Aborted album download"; emit( downloadComplete( false ) ); } void MagnatuneAlbumDownloader::coverAddAborted() { DEBUG_BLOCK m_coverDownloadJob->kill(); m_coverDownloadJob = Q_NULLPTR; debug() << "Aborted cover download"; emit( downloadComplete( false ) ); } diff --git a/src/services/magnatune/MagnatuneStore.cpp b/src/services/magnatune/MagnatuneStore.cpp index e80c35b00a..67665db685 100644 --- a/src/services/magnatune/MagnatuneStore.cpp +++ b/src/services/magnatune/MagnatuneStore.cpp @@ -1,746 +1,746 @@ /**************************************************************************************** * Copyright (c) 2006,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 "MagnatuneStore.h" #include "core/support/Amarok.h" #include "core/support/Components.h" #include "core/interfaces/Logger.h" #include "amarokurls/AmarokUrlHandler.h" #include "browsers/CollectionTreeItem.h" #include "browsers/CollectionTreeView.h" #include "browsers/SingleCollectionTreeItemModel.h" #include "EngineController.h" #include "MagnatuneConfig.h" #include "MagnatuneDatabaseWorker.h" #include "MagnatuneInfoParser.h" #include "MagnatuneNeedUpdateWidget.h" #include "browsers/InfoProxy.h" #include "MagnatuneUrlRunner.h" #include "ui_MagnatuneSignupDialogBase.h" #include "../ServiceSqlRegistry.h" #include "core-impl/collections/support/CollectionManager.h" #include "core/support/Debug.h" #include "playlist/PlaylistModelStack.h" #include "widgets/SearchWidget.h" #include #include #include #include #include #include #include #include #include #include #include //////////////////////////////////////////////////////////////////////////////////////////////////////////// // class MagnatuneServiceFactory //////////////////////////////////////////////////////////////////////////////////////////////////////////// MagnatuneServiceFactory::MagnatuneServiceFactory() : ServiceFactory() { } void MagnatuneServiceFactory::init() { DEBUG_BLOCK MagnatuneStore* service = new MagnatuneStore( this, "Magnatune.com" ); m_initialized = true; emit newService( service ); } QString MagnatuneServiceFactory::name() { return "Magnatune.com"; } KConfigGroup MagnatuneServiceFactory::config() { return Amarok::config( "Service_Magnatune" ); } //////////////////////////////////////////////////////////////////////////////////////////////////////////// // class MagnatuneStore //////////////////////////////////////////////////////////////////////////////////////////////////////////// MagnatuneStore::MagnatuneStore( MagnatuneServiceFactory* parent, const char *name ) : ServiceBase( name, parent ) , m_downloadHandler( 0 ) , m_redownloadHandler( 0 ) , m_needUpdateWidget( 0 ) , m_downloadInProgress( 0 ) , m_currentAlbum( 0 ) , m_streamType( MagnatuneMetaFactory::OGG ) , m_magnatuneTimestamp( 0 ) , m_registry( 0 ) , m_signupInfoWidget( 0 ) { DEBUG_BLOCK setObjectName(name); //initTopPanel( ); setShortDescription( i18n( "\"Fair trade\" online music store" ) ); setIcon( QIcon::fromTheme( "view-services-magnatune-amarok" ) ); // xgettext: no-c-format setLongDescription( i18n( "Magnatune.com is a different kind of record company with the motto \"We are not evil!\" 50% of every purchase goes directly to the artist and if you purchase an album through Amarok, the Amarok project receives a 10% commission. Magnatune.com also offers \"all you can eat\" memberships that lets you download as much of their music as you like." ) ); setImagePath( QStandardPaths::locate( QStandardPaths::GenericDataLocation, "amarok/images/hover_info_magnatune.png" ) ); //initBottomPanel(); // m_currentlySelectedItem = 0; m_polished = false; //polish( ); //FIXME not happening when shown for some reason //do this stuff now to make us function properly as a track provider on startup. The expensive stuff will //not happen untill the model is added to the view anyway. MagnatuneMetaFactory * metaFactory = new MagnatuneMetaFactory( "magnatune", this ); MagnatuneConfig config; if ( config.isMember() ) { setMembership( config.membershipType(), config.username(), config.password() ); metaFactory->setMembershipInfo( config.membershipPrefix(), m_username, m_password ); } setStreamType( config.streamType() ); metaFactory->setStreamType( m_streamType ); m_registry = new ServiceSqlRegistry( metaFactory ); m_collection = new Collections::MagnatuneSqlCollection( "magnatune", "Magnatune.com", metaFactory, m_registry ); CollectionManager::instance()->addTrackProvider( m_collection ); setServiceReady( true ); } MagnatuneStore::~MagnatuneStore() { CollectionManager::instance()->removeTrackProvider( m_collection ); delete m_registry; delete m_collection; } void MagnatuneStore::download( ) { DEBUG_BLOCK if ( m_downloadInProgress ) return; if ( !m_polished ) polish(); debug() << "here"; //check if we need to start a download or show the signup dialog if( !m_isMember || m_membershipType != MagnatuneConfig::DOWNLOAD ) { showSignupDialog(); return; } m_downloadInProgress = true; m_downloadAlbumButton->setEnabled( false ); if ( !m_downloadHandler ) { m_downloadHandler = new MagnatuneDownloadHandler(); m_downloadHandler->setParent( this ); connect( m_downloadHandler, &MagnatuneDownloadHandler::downloadCompleted, this, &MagnatuneStore::downloadCompleted ); } if ( m_currentAlbum != 0 ) m_downloadHandler->downloadAlbum( m_currentAlbum ); } void MagnatuneStore::downloadTrack( Meta::MagnatuneTrack * track ) { Meta::MagnatuneAlbum * album = dynamic_cast( track->album().data() ); if ( album ) downloadAlbum( album ); } void MagnatuneStore::downloadAlbum( Meta::MagnatuneAlbum * album ) { DEBUG_BLOCK if ( m_downloadInProgress ) return; if ( !m_polished ) polish(); m_downloadInProgress = true; m_downloadAlbumButton->setEnabled( false ); if ( !m_downloadHandler ) { m_downloadHandler = new MagnatuneDownloadHandler(); m_downloadHandler->setParent( this ); connect( m_downloadHandler, &MagnatuneDownloadHandler::downloadCompleted, this, &MagnatuneStore::downloadCompleted ); } m_downloadHandler->downloadAlbum( album ); } void MagnatuneStore::initTopPanel( ) { QMenu *filterMenu = new QMenu( 0 ); QAction *action = filterMenu->addAction( i18n("Artist") ); connect( action, &QAction::triggered, this, &MagnatuneStore::sortByArtist ); action = filterMenu->addAction( i18n( "Artist / Album" ) ); connect( action, &QAction::triggered, this, &MagnatuneStore::sortByArtistAlbum ); action = filterMenu->addAction( i18n( "Album" ) ) ; connect( action, &QAction::triggered, this, &MagnatuneStore::sortByAlbum ); action = filterMenu->addAction( i18n( "Genre / Artist" ) ); connect( action, &QAction::triggered, this, &MagnatuneStore::sortByGenreArtist ); action = filterMenu->addAction( i18n( "Genre / Artist / Album" ) ); connect( action, &QAction::triggered, this, &MagnatuneStore::sortByGenreArtistAlbum ); QAction *filterMenuAction = new QAction( QIcon::fromTheme( "preferences-other" ), i18n( "Sort Options" ), this ); filterMenuAction->setMenu( filterMenu ); m_searchWidget->toolBar()->addSeparator(); m_searchWidget->toolBar()->addAction( filterMenuAction ); QToolButton *tbutton = qobject_cast( m_searchWidget->toolBar()->widgetForAction( filterMenuAction ) ); if( tbutton ) tbutton->setPopupMode( QToolButton::InstantPopup ); QMenu * actionsMenu = new QMenu( 0 ); action = actionsMenu->addAction( i18n( "Re-download" ) ); connect( action, &QAction::triggered, this, &MagnatuneStore::processRedownload ); m_updateAction = actionsMenu->addAction( i18n( "Update Database" ) ); connect( m_updateAction, &QAction::triggered, this, &MagnatuneStore::updateButtonClicked ); QAction *actionsMenuAction = new QAction( QIcon::fromTheme( "list-add" ), i18n( "Tools" ), this ); actionsMenuAction->setMenu( actionsMenu ); m_searchWidget->toolBar()->addAction( actionsMenuAction ); tbutton = qobject_cast( m_searchWidget->toolBar()->widgetForAction( actionsMenuAction ) ); if( tbutton ) tbutton->setPopupMode( QToolButton::InstantPopup ); } void MagnatuneStore::initBottomPanel() { //m_bottomPanel->setMaximumHeight( 24 ); m_downloadAlbumButton = new QPushButton; m_downloadAlbumButton->setParent( m_bottomPanel ); MagnatuneConfig config; if ( config.isMember() && config.membershipType() == MagnatuneConfig::DOWNLOAD ) { m_downloadAlbumButton->setText( i18n( "Download Album" ) ); m_downloadAlbumButton->setEnabled( false ); } else if ( config.isMember() ) m_downloadAlbumButton->hide(); else { m_downloadAlbumButton->setText( i18n( "Signup" ) ); m_downloadAlbumButton->setEnabled( true ); } m_downloadAlbumButton->setObjectName( "downloadButton" ); m_downloadAlbumButton->setIcon( QIcon::fromTheme( "download-amarok" ) ); connect( m_downloadAlbumButton, &QPushButton::clicked, this, &MagnatuneStore::download ); if ( !config.lastUpdateTimestamp() ) { m_needUpdateWidget = new MagnatuneNeedUpdateWidget(m_bottomPanel); connect( m_needUpdateWidget, &MagnatuneNeedUpdateWidget::wantUpdate, this, &MagnatuneStore::updateButtonClicked ); m_downloadAlbumButton->setParent(0); } } void MagnatuneStore::updateButtonClicked() { DEBUG_BLOCK m_updateAction->setEnabled( false ); if ( m_needUpdateWidget ) m_needUpdateWidget->disable(); updateMagnatuneList(); } bool MagnatuneStore::updateMagnatuneList() { DEBUG_BLOCK //download new list from magnatune debug() << "MagnatuneStore: start downloading xml file"; QTemporaryFile tempFile; // tempFile.setSuffix( ".bz2" ); tempFile.setAutoRemove( false ); // file will be removed in MagnatuneXmlParser if( !tempFile.open() ) { return false; //error } m_tempFileName = tempFile.fileName(); m_listDownloadJob = KIO::file_copy( QUrl("http://magnatune.com/info/album_info_xml.bz2"), QUrl::fromLocalFile( m_tempFileName ), 0700 , KIO::HideProgressInfo | KIO::Overwrite ); - Amarok::Components::logger()->newProgressOperation( m_listDownloadJob, i18n( "Downloading Magnatune.com database..." ), this, SLOT(listDownloadCancelled()) ); + Amarok::Components::logger()->newProgressOperation( m_listDownloadJob, i18n( "Downloading Magnatune.com database..." ), this, &MagnatuneStore::listDownloadCancelled ); connect( m_listDownloadJob, &KJob::result, this, &MagnatuneStore::listDownloadComplete ); return true; } void MagnatuneStore::listDownloadComplete( KJob * downLoadJob ) { DEBUG_BLOCK debug() << "MagnatuneStore: xml file download complete"; if ( downLoadJob != m_listDownloadJob ) { debug() << "wrong job, ignoring...."; return ; //not the right job, so let's ignore it } m_updateAction->setEnabled( true ); if ( downLoadJob->error() != 0 ) { debug() << "Got an error, bailing out: " << downLoadJob->errorString(); //TODO: error handling here return ; } Amarok::Components::logger()->shortMessage( i18n( "Updating the local Magnatune database." ) ); MagnatuneXmlParser * parser = new MagnatuneXmlParser( m_tempFileName ); parser->setDbHandler( new MagnatuneDatabaseHandler() ); connect( parser, &MagnatuneXmlParser::doneParsing, this, &MagnatuneStore::doneParsing ); ThreadWeaver::Queue::instance()->enqueue( QSharedPointer(parser) ); } void MagnatuneStore::listDownloadCancelled( ) { DEBUG_BLOCK m_listDownloadJob->kill(); m_listDownloadJob = 0; debug() << "Aborted xml download"; m_updateAction->setEnabled( true ); if ( m_needUpdateWidget ) m_needUpdateWidget->enable(); } void MagnatuneStore::doneParsing() { debug() << "MagnatuneStore: done parsing"; m_collection->emitUpdated(); //update the last update timestamp MagnatuneConfig config; if ( m_magnatuneTimestamp == 0 ) config.setLastUpdateTimestamp( QDateTime::currentDateTime().toTime_t() ); else config.setLastUpdateTimestamp( m_magnatuneTimestamp ); config.save(); if ( m_needUpdateWidget ) { m_needUpdateWidget->setParent(0); m_needUpdateWidget->deleteLater(); m_needUpdateWidget = 0; m_downloadAlbumButton->setParent(m_bottomPanel); } } void MagnatuneStore::processRedownload( ) { debug() << "Process redownload"; if ( m_redownloadHandler == 0 ) { m_redownloadHandler = new MagnatuneRedownloadHandler( this ); } m_redownloadHandler->showRedownloadDialog(); } void MagnatuneStore::downloadCompleted( bool ) { delete m_downloadHandler; m_downloadHandler = 0; m_downloadAlbumButton->setEnabled( true ); m_downloadInProgress = false; debug() << "Purchase operation complete"; //TODO: display some kind of success dialog here? } void MagnatuneStore::itemSelected( CollectionTreeItem * selectedItem ) { DEBUG_BLOCK //only care if the user has a download membership if( !m_isMember || m_membershipType != MagnatuneConfig::DOWNLOAD ) return; //we only enable the purchase button if there is only one item selected and it happens to //be an album or a track Meta::DataPtr dataPtr = selectedItem->data(); if ( typeid( * dataPtr.data() ) == typeid( Meta::MagnatuneTrack ) ) { debug() << "is right type (track)"; Meta::MagnatuneTrack * track = static_cast ( dataPtr.data() ); m_currentAlbum = static_cast ( track->album().data() ); m_downloadAlbumButton->setEnabled( true ); } else if ( typeid( * dataPtr.data() ) == typeid( Meta::MagnatuneAlbum ) ) { m_currentAlbum = static_cast ( dataPtr.data() ); debug() << "is right type (album) named " << m_currentAlbum->name(); m_downloadAlbumButton->setEnabled( true ); } else { debug() << "is wrong type"; m_downloadAlbumButton->setEnabled( false ); } } void MagnatuneStore::addMoodyTracksToPlaylist( const QString &mood, int count ) { MagnatuneDatabaseWorker *databaseWorker = new MagnatuneDatabaseWorker(); databaseWorker->fetchTrackswithMood( mood, count, m_registry ); connect( databaseWorker, &MagnatuneDatabaseWorker::gotMoodyTracks, this, &MagnatuneStore::moodyTracksReady ); ThreadWeaver::Queue::instance()->enqueue( QSharedPointer(databaseWorker) ); } void MagnatuneStore::polish() { DEBUG_BLOCK; if (!m_polished) { m_polished = true; initTopPanel( ); initBottomPanel(); QList levels; levels << CategoryId::Genre << CategoryId::Artist << CategoryId::Album; m_magnatuneInfoParser = new MagnatuneInfoParser(); setInfoParser( m_magnatuneInfoParser ); setModel( new SingleCollectionTreeItemModel( m_collection, levels ) ); connect( qobject_cast(m_contentView), &CollectionTreeView::itemSelected, this, &MagnatuneStore::itemSelected ); //add a custom url runner MagnatuneUrlRunner *runner = new MagnatuneUrlRunner(); connect( runner, &MagnatuneUrlRunner::showFavorites, this, &MagnatuneStore::showFavoritesPage ); connect( runner, &MagnatuneUrlRunner::showHome, this, &MagnatuneStore::showHomePage ); connect( runner, &MagnatuneUrlRunner::showRecommendations, this, &MagnatuneStore::showRecommendationsPage ); connect( runner, &MagnatuneUrlRunner::buyOrDownload, this, &MagnatuneStore::downloadSku ); connect( runner, &MagnatuneUrlRunner::removeFromFavorites, this, &MagnatuneStore::removeFromFavorites ); The::amarokUrlHandler()->registerRunner( runner, runner->command() ); } QString imagePath = QStandardPaths::locate( QStandardPaths::GenericDataLocation, "amarok/data/" ); const QUrl url = QUrl::fromLocalFile( imagePath ); MagnatuneInfoParser * parser = dynamic_cast ( infoParser() ); if ( parser ) parser->getFrontPage(); //get a mood map we can show to the cloud view MagnatuneDatabaseWorker * databaseWorker = new MagnatuneDatabaseWorker(); databaseWorker->fetchMoodMap(); connect( databaseWorker, &MagnatuneDatabaseWorker::gotMoodMap, this, &MagnatuneStore::moodMapReady ); ThreadWeaver::Queue::instance()->enqueue( QSharedPointer(databaseWorker) ); if ( MagnatuneConfig().autoUpdateDatabase() ) checkForUpdates(); } void MagnatuneStore::setMembership( int type, const QString & username, const QString & password) { m_isMember = true; m_membershipType = type; m_username = username; m_password = password; } void MagnatuneStore::moodMapReady(QMap< QString, int > map) { QVariantMap variantMap; QList strings; QList weights; QVariantMap dbusActions; foreach( const QString &key, map.keys() ) { strings << key; weights << map.value( key ); QString escapedKey = key; escapedKey.replace( ' ', "%20" ); QVariantMap action; action["component"] = "/ServicePluginManager"; action["function"] = "sendMessage"; action["arg1"] = QString( "Magnatune.com"); action["arg2"] = QString( "addMoodyTracks %1 10").arg( escapedKey ); dbusActions[key] = action; } variantMap["cloud_name"] = QVariant( "Magnatune Moods" ); variantMap["cloud_strings"] = QVariant( strings ); variantMap["cloud_weights"] = QVariant( weights ); variantMap["cloud_actions"] = QVariant( dbusActions ); The::infoProxy()->setCloud( variantMap ); } void MagnatuneStore::setStreamType( int type ) { m_streamType = type; } void MagnatuneStore::checkForUpdates() { m_updateTimestampDownloadJob = KIO::storedGet( QUrl("http://magnatune.com/info/last_update_timestamp"), KIO::Reload, KIO::HideProgressInfo ); connect( m_updateTimestampDownloadJob, &KJob::result, this, &MagnatuneStore::timestampDownloadComplete ); } void MagnatuneStore::timestampDownloadComplete( KJob * job ) { DEBUG_BLOCK if ( job->error() != 0 ) { //TODO: error handling here return ; } if ( job != m_updateTimestampDownloadJob ) return ; //not the right job, so let's ignore it QString timestampString = ( ( KIO::StoredTransferJob* ) job )->data(); debug() << "Magnatune timestamp: " << timestampString; bool ok; qulonglong magnatuneTimestamp = timestampString.toULongLong( &ok ); MagnatuneConfig config; qulonglong localTimestamp = config.lastUpdateTimestamp(); debug() << "Last update timestamp: " << QString::number( localTimestamp ); if ( ok && magnatuneTimestamp > localTimestamp ) { m_magnatuneTimestamp = magnatuneTimestamp; updateButtonClicked(); } } void MagnatuneStore::moodyTracksReady( Meta::TrackList tracks ) { DEBUG_BLOCK The::playlistController()->insertOptioned( tracks, Playlist::Replace ); } QString MagnatuneStore::messages() { QString text = i18n( "The Magnatune.com service accepts the following messages: \n\n\taddMoodyTracks mood count: Adds a number of random tracks with the specified mood to the playlist. The mood argument must have spaces escaped with %%20" ); return text; } QString MagnatuneStore::sendMessage( const QString & message ) { QStringList args = message.split( ' ', QString::SkipEmptyParts ); if ( args.size() < 1 ) { return i18n( "ERROR: No arguments supplied" ); } if ( args[0] == "addMoodyTracks" ) { if ( args.size() != 3 ) { return i18n( "ERROR: Wrong number of arguments for addMoodyTracks" ); } QString mood = args[1]; mood = mood.replace( "%20", " " ); bool ok; int count = args[2].toInt( &ok ); if ( !ok ) return i18n( "ERROR: Parse error for argument 2 ( count )" ); addMoodyTracksToPlaylist( mood, count ); return i18n( "ok" ); } return i18n( "ERROR: Unknown argument." ); } void MagnatuneStore::showFavoritesPage() { DEBUG_BLOCK m_magnatuneInfoParser->getFavoritesPage(); } void MagnatuneStore::showHomePage() { DEBUG_BLOCK m_magnatuneInfoParser->getFrontPage(); } void MagnatuneStore::showRecommendationsPage() { DEBUG_BLOCK m_magnatuneInfoParser->getRecommendationsPage(); } void MagnatuneStore::downloadSku( const QString &sku ) { DEBUG_BLOCK debug() << "sku: " << sku; MagnatuneDatabaseWorker * databaseWorker = new MagnatuneDatabaseWorker(); databaseWorker->fetchAlbumBySku( sku, m_registry ); connect( databaseWorker, &MagnatuneDatabaseWorker::gotAlbumBySku, this, &MagnatuneStore::downloadAlbum ); ThreadWeaver::Queue::instance()->enqueue( QSharedPointer(databaseWorker) ); } void MagnatuneStore::addToFavorites( const QString &sku ) { DEBUG_BLOCK MagnatuneConfig config; if( !config.isMember() ) return; QString url = "http://%1:%2@%3.magnatune.com/member/favorites?action=add_api&sku=%4"; url = url.arg( config.username(), config.password(), config.membershipPrefix(), sku ); debug() << "favorites url: " << url; m_favoritesJob = KIO::storedGet( QUrl( url ), KIO::Reload, KIO::HideProgressInfo ); connect( m_favoritesJob, &KJob::result, this, &MagnatuneStore::favoritesResult ); } void MagnatuneStore::removeFromFavorites( const QString &sku ) { DEBUG_BLOCK MagnatuneConfig config; if( !config.isMember() ) return; QString url = "http://%1:%2@%3.magnatune.com/member/favorites?action=remove_api&sku=%4"; url = url.arg( config.username(), config.password(), config.membershipPrefix(), sku ); debug() << "favorites url: " << url; m_favoritesJob = KIO::storedGet( QUrl( url ), KIO::Reload, KIO::HideProgressInfo ); connect( m_favoritesJob, &KJob::result, this, &MagnatuneStore::favoritesResult ); } void MagnatuneStore::favoritesResult( KJob* addToFavoritesJob ) { if( addToFavoritesJob != m_favoritesJob ) return; QString result = m_favoritesJob->data(); Amarok::Components::logger()->longMessage( result ); //show the favorites page showFavoritesPage(); } void MagnatuneStore::showSignupDialog() { if ( m_signupInfoWidget== 0 ) { m_signupInfoWidget = new QDialog; Ui::SignupDialog ui; ui.setupUi( m_signupInfoWidget ); } m_signupInfoWidget->show(); } diff --git a/src/statsyncing/Controller.cpp b/src/statsyncing/Controller.cpp index a2a18c4c68..10421f6f78 100644 --- a/src/statsyncing/Controller.cpp +++ b/src/statsyncing/Controller.cpp @@ -1,449 +1,449 @@ /**************************************************************************************** * 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 "Controller.h" #include "EngineController.h" #include "MainWindow.h" #include "ProviderFactory.h" #include "amarokconfig.h" #include "core/collections/Collection.h" #include "core/interfaces/Logger.h" #include "core/meta/Meta.h" #include "core/support/Amarok.h" #include "core/support/Components.h" #include "core/support/Debug.h" #include "statsyncing/Config.h" #include "statsyncing/Process.h" #include "statsyncing/ScrobblingService.h" #include "statsyncing/collection/CollectionProvider.h" #include "statsyncing/ui/CreateProviderDialog.h" #include "statsyncing/ui/ConfigureProviderDialog.h" #include "MetaValues.h" #include #include using namespace StatSyncing; const int Controller::s_syncingTriggerTimeout( 5000 ); Controller::Controller( QObject* parent ) : QObject( parent ) , m_startSyncingTimer( new QTimer( this ) ) , m_config( new Config( this ) ) , m_updateNowPlayingTimer( new QTimer( this ) ) { qRegisterMetaType(); m_startSyncingTimer->setSingleShot( true ); connect( m_startSyncingTimer, &QTimer::timeout, this, &Controller::startNonInteractiveSynchronization ); CollectionManager *manager = CollectionManager::instance(); Q_ASSERT( manager ); connect( manager, &CollectionManager::collectionAdded, this, &Controller::slotCollectionAdded ); connect( manager, &CollectionManager::collectionRemoved, this, &Controller::slotCollectionRemoved ); delayedStartSynchronization(); EngineController *engine = Amarok::Components::engineController(); Q_ASSERT( engine ); connect( engine, &EngineController::trackFinishedPlaying, this, &Controller::slotTrackFinishedPlaying ); m_updateNowPlayingTimer->setSingleShot( true ); m_updateNowPlayingTimer->setInterval( 10000 ); // wait 10s before updating // We connect the signals to (re)starting the timer to postpone the submission a // little to prevent frequent updates of rapidly - changing metadata connect( engine, &EngineController::trackChanged, m_updateNowPlayingTimer, QOverload<>::of(&QTimer::start) ); // following is needed for streams that don't emit newTrackPlaying on song change connect( engine, &EngineController::trackMetadataChanged, m_updateNowPlayingTimer, QOverload<>::of(&QTimer::start) ); connect( m_updateNowPlayingTimer, &QTimer::timeout, this, &Controller::slotUpdateNowPlayingWithCurrentTrack ); // we need to reset m_lastSubmittedNowPlayingTrack when a track is played twice connect( engine, &EngineController::trackPlaying, this, &Controller::slotResetLastSubmittedNowPlayingTrack ); } Controller::~Controller() { } QList Controller::availableFields() { // when fields are changed, please update translations in MetadataConfig::MetadataConfig() return QList() << Meta::valRating << Meta::valFirstPlayed << Meta::valLastPlayed << Meta::valPlaycount << Meta::valLabel; } void Controller::registerProvider( const ProviderPtr &provider ) { QString id = provider->id(); bool enabled = false; if( m_config->providerKnown( id ) ) enabled = m_config->providerEnabled( id, false ); else { switch( provider->defaultPreference() ) { case Provider::Never: case Provider::NoByDefault: enabled = false; break; case Provider::Ask: { QString text = i18nc( "%1 is collection name", "%1 has an ability to " "synchronize track meta-data such as play count or rating " "with other collections. Do you want to keep %1 synchronized?\n\n" "You can always change the decision in Amarok configuration.", provider->prettyName() ); enabled = KMessageBox::questionYesNo( The::mainWindow(), text ) == KMessageBox::Yes; break; } case Provider::YesByDefault: enabled = true; break; } } // don't tell config about Never-by-default providers if( provider->defaultPreference() != Provider::Never ) { m_config->updateProvider( id, provider->prettyName(), provider->icon(), true, enabled ); m_config->save(); } m_providers.append( provider ); connect( provider.data(), &StatSyncing::Provider::updated, this, &Controller::slotProviderUpdated ); if( enabled ) delayedStartSynchronization(); } void Controller::unregisterProvider( const ProviderPtr &provider ) { disconnect( provider.data(), 0, this, 0 ); if( m_config->providerKnown( provider->id() ) ) { m_config->updateProvider( provider->id(), provider->prettyName(), provider->icon(), /* online */ false ); m_config->save(); } m_providers.removeAll( provider ); } void -Controller::setFactories( const QList &factories ) +Controller::setFactories( const QList > &factories ) { - foreach( Plugins::PluginFactory *pFactory, factories ) + for( const auto &pFactory : factories ) { - ProviderFactory *factory = qobject_cast( pFactory ); + auto factory = qobject_cast( pFactory ); if( !factory ) continue; if( m_providerFactories.contains( factory->type() ) ) // we have it already continue; m_providerFactories.insert( factory->type(), factory ); } } bool Controller::hasProviderFactories() const { return !m_providerFactories.isEmpty(); } bool Controller::providerIsConfigurable( const QString &id ) const { ProviderPtr provider = findRegisteredProvider( id ); return provider ? provider->isConfigurable() : false; } QWidget* Controller::providerConfigDialog( const QString &id ) const { ProviderPtr provider = findRegisteredProvider( id ); if( !provider || !provider->isConfigurable() ) return 0; ConfigureProviderDialog *dialog = new ConfigureProviderDialog( id, provider->configWidget(), The::mainWindow() ); connect( dialog, &StatSyncing::ConfigureProviderDialog::providerConfigured, this, &Controller::reconfigureProvider ); connect( dialog, &StatSyncing::ConfigureProviderDialog::finished, dialog, &StatSyncing::ConfigureProviderDialog::deleteLater ); return dialog; } QWidget* Controller::providerCreationDialog() const { CreateProviderDialog *dialog = new CreateProviderDialog( The::mainWindow() ); - foreach( ProviderFactory * const factory, m_providerFactories ) + for( const auto &factory : m_providerFactories ) dialog->addProviderType( factory->type(), factory->prettyName(), factory->icon(), factory->createConfigWidget() ); connect( dialog, &StatSyncing::CreateProviderDialog::providerConfigured, this, &Controller::createProvider ); connect( dialog, &StatSyncing::CreateProviderDialog::finished, dialog, &StatSyncing::CreateProviderDialog::deleteLater ); return dialog; } void Controller::createProvider( QString type, QVariantMap config ) { Q_ASSERT( m_providerFactories.contains( type ) ); m_providerFactories[type]->createProvider( config ); } void Controller::reconfigureProvider( QString id, QVariantMap config ) { ProviderPtr provider = findRegisteredProvider( id ); if( provider ) provider->reconfigure( config ); } void Controller::registerScrobblingService( const ScrobblingServicePtr &service ) { if( m_scrobblingServices.contains( service ) ) { warning() << __PRETTY_FUNCTION__ << "scrobbling service" << service << "already registered"; return; } m_scrobblingServices << service; } void Controller::unregisterScrobblingService( const ScrobblingServicePtr &service ) { m_scrobblingServices.removeAll( service ); } QList Controller::scrobblingServices() const { return m_scrobblingServices; } Config * Controller::config() { return m_config; } void Controller::synchronize() { synchronizeWithMode( Process::Interactive ); } void Controller::scrobble( const Meta::TrackPtr &track, double playedFraction, const QDateTime &time ) { foreach( ScrobblingServicePtr service, m_scrobblingServices ) { ScrobblingService::ScrobbleError error = service->scrobble( track, playedFraction, time ); if( error == ScrobblingService::NoError ) emit trackScrobbled( service, track ); else emit scrobbleFailed( service, track, error ); } } void Controller::slotProviderUpdated() { QObject *updatedProvider = sender(); Q_ASSERT( updatedProvider ); foreach( const ProviderPtr &provider, m_providers ) { if( provider.data() == updatedProvider ) { m_config->updateProvider( provider->id(), provider->prettyName(), provider->icon(), true ); m_config->save(); } } } void Controller::delayedStartSynchronization() { if( m_startSyncingTimer->isActive() ) m_startSyncingTimer->start( s_syncingTriggerTimeout ); // reset the timeout else { m_startSyncingTimer->start( s_syncingTriggerTimeout ); // we could as well connect to all m_providers updated signals, but this serves // for now CollectionManager *manager = CollectionManager::instance(); Q_ASSERT( manager ); connect( manager, &CollectionManager::collectionDataChanged, this, &Controller::delayedStartSynchronization ); } } void Controller::slotCollectionAdded( Collections::Collection *collection, CollectionManager::CollectionStatus status ) { if( status != CollectionManager::CollectionEnabled ) return; ProviderPtr provider( new CollectionProvider( collection ) ); registerProvider( provider ); } void Controller::slotCollectionRemoved( const QString &id ) { // here we depend on StatSyncing::CollectionProvider returning identical id // as collection ProviderPtr provider = findRegisteredProvider( id ); if( provider ) unregisterProvider( provider ); } void Controller::startNonInteractiveSynchronization() { CollectionManager *manager = CollectionManager::instance(); Q_ASSERT( manager ); disconnect( manager, &CollectionManager::collectionDataChanged, this, &Controller::delayedStartSynchronization ); synchronizeWithMode( Process::NonInteractive ); } void Controller::synchronizeWithMode( int intMode ) { Process::Mode mode = Process::Mode( intMode ); if( m_currentProcess ) { if( mode == StatSyncing::Process::Interactive ) m_currentProcess->raise(); return; } // read saved config qint64 fields = m_config->checkedFields(); if( mode == Process::NonInteractive && fields == 0 ) return; // nothing to do ProviderPtrSet checkedProviders; foreach( ProviderPtr provider, m_providers ) { if( m_config->providerEnabled( provider->id(), false ) ) checkedProviders.insert( provider ); } ProviderPtrList usedProviders; switch( mode ) { case Process::Interactive: usedProviders = m_providers; break; case Process::NonInteractive: usedProviders = checkedProviders.toList(); break; } if( usedProviders.isEmpty() ) return; // nothing to do if( usedProviders.count() == 1 && usedProviders.first()->id() == "localCollection" ) { if( mode == StatSyncing::Process::Interactive ) { QString text = i18n( "You only seem to have the Local Collection. Statistics " "synchronization only makes sense if there is more than one collection." ); Amarok::Components::logger()->longMessage( text ); } return; } m_currentProcess = new Process( m_providers, checkedProviders, fields, mode, this ); m_currentProcess->start(); } void Controller::slotTrackFinishedPlaying( const Meta::TrackPtr &track, double playedFraction ) { if( !AmarokConfig::submitPlayedSongs() ) return; Q_ASSERT( track ); scrobble( track, playedFraction ); } void Controller::slotResetLastSubmittedNowPlayingTrack() { m_lastSubmittedNowPlayingTrack = Meta::TrackPtr(); } void Controller::slotUpdateNowPlayingWithCurrentTrack() { EngineController *engine = Amarok::Components::engineController(); if( !engine ) return; Meta::TrackPtr track = engine->currentTrack(); // null track is okay if( tracksVirtuallyEqual( track, m_lastSubmittedNowPlayingTrack ) ) { debug() << __PRETTY_FUNCTION__ << "this track already recently submitted, ignoring"; return; } foreach( ScrobblingServicePtr service, m_scrobblingServices ) { service->updateNowPlaying( track ); } m_lastSubmittedNowPlayingTrack = track; } ProviderPtr Controller::findRegisteredProvider( const QString &id ) const { foreach( const ProviderPtr &provider, m_providers ) if( provider->id() == id ) return provider; return ProviderPtr(); } bool Controller::tracksVirtuallyEqual( const Meta::TrackPtr &first, const Meta::TrackPtr &second ) { if( !first && !second ) return true; // both null if( !first || !second ) return false; // exactly one is null const QString firstAlbum = first->album() ? first->album()->name() : QString(); const QString secondAlbum = second->album() ? second->album()->name() : QString(); const QString firstArtist = first->artist() ? first->artist()->name() : QString(); const QString secondArtist = second->artist() ? second->artist()->name() : QString(); return first->name() == second->name() && firstAlbum == secondAlbum && firstArtist == secondArtist; } diff --git a/src/statsyncing/Controller.h b/src/statsyncing/Controller.h index f8dafe9d66..142e70dea7 100644 --- a/src/statsyncing/Controller.h +++ b/src/statsyncing/Controller.h @@ -1,243 +1,243 @@ /**************************************************************************************** * 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 STATSYNCING_CONTROLLER_H #define STATSYNCING_CONTROLLER_H #include "amarok_export.h" // for CollectionManager::CollectionStatus that cannont be fwd-declared #include "core-impl/collections/support/CollectionManager.h" #include #include #include #include class QTimer; namespace StatSyncing { class Config; class CreateProviderDialog; class Process; class Provider; typedef QSharedPointer ProviderPtr; typedef QList ProviderPtrList; class ProviderFactory; class ScrobblingService; typedef QSharedPointer ScrobblingServicePtr; /** * A singleton class that controls statistics synchronization and related tasks. */ class AMAROK_EXPORT Controller : public QObject { Q_OBJECT public: explicit Controller( QObject *parent = 0 ); ~Controller(); /** * Return a list of Meta::val* fields that statistics synchronization can * actually synchronize. */ static QList availableFields(); /** * Register a StatSyncing::Provider with StatSyncing controller. This makes * it possible to synchronize provider with other providers. You don't need * to call this for Collections that are registered through CollectionManager * (and marked as enabled there) as it is done automatically. */ virtual void registerProvider( const ProviderPtr &provider ); /** * Forget about StatSyncing::Provider @param provider. */ virtual void unregisterProvider( const ProviderPtr &provider ); /** * Handle plugin factories derived from ProviderFactory, used for creating * multiple provider instances. This method is called by Amarok's plugin * infrastructure. */ - void setFactories( const QList &factories ); + void setFactories( const QList > &factories ); /** * Returns true if any instantiable provider types are registered with the * controller. */ bool hasProviderFactories() const; /** * Returns true if the provider identified by @param id is configurable */ bool providerIsConfigurable( const QString &id ) const; /** * Returns a configuration dialog for a provider identified by @param id . * @returns 0 if there's no provider identified by id or the provider is not * configurable, otherwise a pointer to the dialog constructed as a child of * The::mainWindow */ QWidget *providerConfigDialog( const QString &id ) const; /** * Returns a provider creation dialog, prepopulated with registered provider * types. * @returns a pointer to the dialog constructed as a child of The::mainWindow, * and is a subclass of KAssistantDialog. * * @see StatSyncing::CreateProviderDialog */ QWidget *providerCreationDialog() const; /** * Register ScrobblingService with StatSyncing controller. Controller then * listens to EngineController and calls scrobble() etc. when user plays * tracks. Also allows scrobbling for tracks played on just connected iPods. * * @param service */ void registerScrobblingService( const ScrobblingServicePtr &service ); /** * Forget about ScrobblingService @param service */ void unregisterScrobblingService( const ScrobblingServicePtr &service ); /** * Return a list of currently registered scrobbling services (in arbitrary * order). */ QList scrobblingServices() const; /** * Return StatSyncing configuration object that describes enabled and * disabled statsyncing providers. You may not cache the pointer. */ Config *config(); public Q_SLOTS: /** * Start the whole synchronization machinery. This call returns quickly, * way before the synchronization is finished. */ void synchronize(); /** * Scrobble a track using all registered scrobbling services. They may check * certain criteria such as track length and refuse to scrobble the track. * * @param track track to scrobble * @param playedFraction fraction which has been actually played, or a number * greater than 1 if the track was played multiple times * (for example on a media device) * @param time time when it was played, invalid QDateTime signifies that the * track has been played just now. This is the default when the * parameter is omitted. */ void scrobble( const Meta::TrackPtr &track, double playedFraction = 1.0, const QDateTime &time = QDateTime() ); Q_SIGNALS: /** * Emitted when a track passed to scrobble() is successfully queued for * scrobbling submission. This signal is emitted for every scrobbling service. * For each service, you either get this or scrobbleFailed(). */ void trackScrobbled( const ScrobblingServicePtr &service, const Meta::TrackPtr &track ); /** * Emitted when a scrobbling service @service was unable to scrobble() a track. * * @param error is a ScrobblingService::ScrobbleError enum value. */ void scrobbleFailed( const ScrobblingServicePtr &service, const Meta::TrackPtr &track, int error ); private Q_SLOTS: /** * Creates new instance of provider type identified by @param type * with configuration stored in @param config. */ void createProvider( QString type, QVariantMap config ); /** * Reconfigures provider identified by @param id with configuration * stored in @param config. */ void reconfigureProvider( QString id, QVariantMap config ); /** * Can only be connected to provider changed() signal */ void slotProviderUpdated(); /** * Wait a few seconds and if no collectionUpdate() signal arrives until then, * start synchronization. Otherwise postpone the synchronization for a few * seconds. */ void delayedStartSynchronization(); void slotCollectionAdded( Collections::Collection* collection, CollectionManager::CollectionStatus status ); void slotCollectionRemoved( const QString &id ); void startNonInteractiveSynchronization(); void synchronizeWithMode( int mode ); void slotTrackFinishedPlaying( const Meta::TrackPtr &track, double playedFraction ); void slotResetLastSubmittedNowPlayingTrack(); void slotUpdateNowPlayingWithCurrentTrack(); private: Q_DISABLE_COPY( Controller ) ProviderPtr findRegisteredProvider( const QString &id ) const; /** * Return true if important metadata of both tracks is equal. */ bool tracksVirtuallyEqual( const Meta::TrackPtr &first, const Meta::TrackPtr &second ); - QMap m_providerFactories; + QMap > m_providerFactories; // synchronization-related ProviderPtrList m_providers; QPointer m_currentProcess; QTimer *m_startSyncingTimer; Config *m_config; /** * When a new collection appears, StatSyncing::Controller will automatically * trigger synchronization. It however waits s_syncingTriggerTimeout * milliseconds to let the collection settle down. Moreover, if the newly * added collection emits updated(), the timeout will run from start again. * * (reason: e.g. iPod Collectin appears quickly, but with no tracks, which * are added gradually as they are parsed. This "ensures" we only start * syncing as soon as all tracks are parsed.) */ static const int s_syncingTriggerTimeout; // scrobbling-related QList m_scrobblingServices; QTimer *m_updateNowPlayingTimer; Meta::TrackPtr m_lastSubmittedNowPlayingTrack; }; } // namespace StatSyncing #endif // STATSYNCING_CONTROLLER_H diff --git a/src/statsyncing/Process.cpp b/src/statsyncing/Process.cpp index a57f7be425..a24cc2db55 100644 --- a/src/statsyncing/Process.cpp +++ b/src/statsyncing/Process.cpp @@ -1,319 +1,319 @@ /**************************************************************************************** * 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 "Process.h" #include "MainWindow.h" #include "MetaValues.h" #include "core/interfaces/Logger.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include "core/support/Components.h" #include "statsyncing/Config.h" #include "statsyncing/Controller.h" #include "statsyncing/jobs/MatchTracksJob.h" #include "statsyncing/jobs/SynchronizeTracksJob.h" #include "statsyncing/models/MatchedTracksModel.h" #include "statsyncing/models/ProvidersModel.h" #include "statsyncing/models/SingleTracksModel.h" #include "statsyncing/ui/ChooseProvidersPage.h" #include "statsyncing/ui/MatchedTracksPage.h" #include using namespace StatSyncing; Process::Process( const ProviderPtrList &providers, const ProviderPtrSet &preSelectedProviders, qint64 checkedFields, Process::Mode mode, QObject *parent ) : QObject( parent ) , m_mode( mode ) , m_providersModel( new ProvidersModel( providers, preSelectedProviders, this ) ) , m_checkedFields( checkedFields ) , m_matchedTracksModel( 0 ) , m_dialog( new QDialog() ) { m_dialog->setWindowTitle( i18n( "Synchronize Statistics" ) ); m_dialog->resize( QSize( 860, 500 ) ); KWindowConfig::restoreWindowSize( m_dialog->windowHandle(), Amarok::config( "StatSyncingDialog" ) ); // delete this process when user hits the close button connect( m_dialog.data(), &QDialog::finished, this, &Process::slotSaveSizeAndDelete ); /* we need to delete all QWidgets on application exit well before QApplication * is destroyed. We however don't set MainWindow as parent as this would make * StatSyncing dialog share taskbar entry with Amarok main window */ connect( The::mainWindow(), &MainWindow::destroyed, this, &Process::slotDeleteDialog ); } Process::~Process() { delete m_dialog.data(); // we cannot deleteLater, dialog references m_matchedTracksModel } void Process::start() { if( m_mode == Interactive ) { m_providersPage = new ChooseProvidersPage(); m_providersPage->setFields( Controller::availableFields(), m_checkedFields ); m_providersPage->setProvidersModel( m_providersModel, m_providersModel->selectionModel() ); connect( m_providersPage.data(), &StatSyncing::ChooseProvidersPage::accepted, this, &Process::slotMatchTracks ); connect( m_providersPage.data(), &StatSyncing::ChooseProvidersPage::rejected, this, &Process::slotSaveSizeAndDelete ); for( const auto &child : m_dialog->children() ) { auto widget = qobject_cast( child ); if( widget ) { widget->hide(); // otherwise it may last as a ghost image widget->deleteLater(); } } m_providersPage->setParent( m_dialog ); // takes ownership raise(); } else if( m_checkedFields ) slotMatchTracks(); } void Process::raise() { if( m_providersPage || m_tracksPage ) { m_dialog->show(); m_dialog->activateWindow(); m_dialog->raise(); } else m_mode = Interactive; // schedule dialog should be shown when something happens } void Process::slotMatchTracks() { MatchTracksJob *job = new MatchTracksJob( m_providersModel->selectedProviders() ); QString text = i18n( "Matching Tracks for Statistics Synchronization" ); if( m_providersPage ) { ChooseProvidersPage *page = m_providersPage.data(); // too lazy to type m_checkedFields = page->checkedFields(); page->disableControls(); page->setProgressBarText( text ); connect( job, &StatSyncing::MatchTracksJob::totalSteps, page, &StatSyncing::ChooseProvidersPage::setProgressBarMaximum ); connect( job, &StatSyncing::MatchTracksJob::incrementProgress, page, &StatSyncing::ChooseProvidersPage::progressBarIncrementProgress ); connect( page, &StatSyncing::ChooseProvidersPage::rejected, job, &StatSyncing::MatchTracksJob::abort ); connect( m_dialog.data(), &QDialog::finished, job, &StatSyncing::MatchTracksJob::abort ); } else // background operation { - Amarok::Components::logger()->newProgressOperation( job, text, 100, job, SLOT(abort()) ); + Amarok::Components::logger()->newProgressOperation( job, text, 100, job, &MatchTracksJob::abort ); } connect( job, &StatSyncing::MatchTracksJob::done, this, &Process::slotTracksMatched ); connect( job, &StatSyncing::MatchTracksJob::done, job, &StatSyncing::MatchTracksJob::deleteLater ); ThreadWeaver::Queue::instance()->enqueue( QSharedPointer(job) ); } void Process::slotTracksMatched( ThreadWeaver::JobPointer job ) { MatchTracksJob *matchJob = dynamic_cast( job.data() ); if( !matchJob ) { error() << __PRETTY_FUNCTION__ << "Failed cast, should never happen"; deleteLater(); return; } if( !matchJob->success() ) { warning() << __PRETTY_FUNCTION__ << "MatchTracksJob failed"; deleteLater(); return; } StatSyncing::Controller *controller = Amarok::Components::statSyncingController(); if( !controller ) { warning() << __PRETTY_FUNCTION__ << "StatSyncing::Controller disappeared"; deleteLater(); return; } // remove fields that are not writable: qint64 usedFields = m_checkedFields & m_providersModel->writableTrackStatsDataUnion(); m_options.setSyncedFields( usedFields ); m_options.setExcludedLabels( controller->config()->excludedLabels() ); QList columns = QList() << Meta::valTitle; foreach( qint64 field, Controller::availableFields() ) { if( field & usedFields ) columns << field; } m_matchedTracksModel = new MatchedTracksModel( matchJob->matchedTuples(), columns, m_options, this ); QList services = controller->scrobblingServices(); // only fill in m_tracksToScrobble if there are actual scrobbling services available m_tracksToScrobble = services.isEmpty() ? TrackList() : matchJob->tracksToScrobble(); if( m_matchedTracksModel->hasConflict() || m_mode == Interactive ) { m_tracksPage = new MatchedTracksPage(); m_tracksPage->setProviders( matchJob->providers() ); m_tracksPage->setMatchedTracksModel( m_matchedTracksModel ); foreach( ProviderPtr provider, matchJob->providers() ) { if( !matchJob->uniqueTracks().value( provider ).isEmpty() ) m_tracksPage->addUniqueTracksModel( provider, new SingleTracksModel( matchJob->uniqueTracks().value( provider ), columns, m_options, m_tracksPage ) ); if( !matchJob->excludedTracks().value( provider ).isEmpty() ) m_tracksPage->addExcludedTracksModel( provider, new SingleTracksModel( matchJob->excludedTracks().value( provider ), columns, m_options, m_tracksPage ) ); } m_tracksPage->setTracksToScrobble( m_tracksToScrobble, services ); connect( m_tracksPage, &StatSyncing::MatchedTracksPage::back, this, &Process::slotBack ); connect( m_tracksPage, &StatSyncing::MatchedTracksPage::accepted, this, &Process::slotSynchronize ); connect( m_tracksPage, &StatSyncing::MatchedTracksPage::rejected, this, &Process::slotSaveSizeAndDelete ); for( const auto &child : m_dialog->children() ) { auto widget = qobject_cast( child ); if( widget ) { widget->hide(); // otherwise it may last as a ghost image widget->deleteLater(); } } m_tracksPage->setParent( m_dialog ); // takes ownership raise(); } else // NonInteractive mode without conflict slotSynchronize(); } void Process::slotBack() { m_mode = Interactive; // reset mode, we're interactive from this point start(); } void Process::slotSynchronize() { // disconnect, otherwise we prematurely delete Process and thus m_matchedTracksModel disconnect( m_dialog.data(), &QDialog::finished, this, &Process::slotSaveSizeAndDelete ); m_dialog.data()->close(); SynchronizeTracksJob *job = new SynchronizeTracksJob( m_matchedTracksModel->matchedTuples(), m_tracksToScrobble, m_options ); QString text = i18n( "Synchronizing Track Statistics" ); - Amarok::Components::logger()->newProgressOperation( job, text, 100, job, SLOT(abort()) ); + Amarok::Components::logger()->newProgressOperation( job, text, 100, job, &SynchronizeTracksJob::abort ); connect( job, &StatSyncing::SynchronizeTracksJob::done, this, &Process::slotLogSynchronization ); connect( job, &StatSyncing::SynchronizeTracksJob::done, job, &StatSyncing::SynchronizeTracksJob::deleteLater ); ThreadWeaver::Queue::instance()->enqueue( QSharedPointer(job) ); } void Process::slotLogSynchronization( ThreadWeaver::JobPointer job ) { deleteLater(); // our work is done SynchronizeTracksJob *syncJob = dynamic_cast( job.data() ); if( !syncJob ) { warning() << __PRETTY_FUNCTION__ << "syncJob is null"; return; } int updatedTracksCount = syncJob->updatedTracksCount(); QMap > scrobbles = syncJob->scrobbles(); QStringList providerNames; foreach( ProviderPtr provider, m_providersModel->selectedProviders() ) providerNames << "" + provider->prettyName() + ""; QString providers = providerNames.join( i18nc( "comma between list words", ", " ) ); QStringList text = QStringList() << i18ncp( "%2 is a list of collection names", "Synchronization of %2 done. One track was updated.", "Synchronization of %2 done. %1 tracks were updated.", updatedTracksCount, providers ); QMap scrobbleErrorCounts; foreach( const ScrobblingServicePtr &provider, scrobbles.keys() ) { QString name = "" + provider->prettyName() + ""; QMap &providerScrobbles = scrobbles[ provider ]; QMapIterator it( providerScrobbles ); while( it.hasNext() ) { it.next(); if( it.key() == ScrobblingService::NoError ) text << i18np( "One track was queued for scrobbling to %2.", "%1 tracks were queued for scrobbling to %2.", it.value(), name ); else scrobbleErrorCounts[ it.key() ] += it.value(); } } if( scrobbleErrorCounts.value( ScrobblingService::TooShort ) ) text << i18np( "One track's played time was too short to be scrobbled.", "%1 tracks' played time was too short to be scrobbled.", scrobbleErrorCounts[ ScrobblingService::TooShort ] ); if( scrobbleErrorCounts.value( ScrobblingService::BadMetadata ) ) text << i18np( "One track had insufficient metadata to be scrobbled.", "%1 tracks had insufficient metadata to be scrobbled.", scrobbleErrorCounts[ ScrobblingService::BadMetadata ] ); if( scrobbleErrorCounts.value( ScrobblingService::FromTheFuture ) ) text << i18np( "One track was reported to have been played in the future.", "%1 tracks were reported to have been played in the future.", scrobbleErrorCounts[ ScrobblingService::FromTheFuture ] ); if( scrobbleErrorCounts.value( ScrobblingService::FromTheDistantPast ) ) text << i18np( "One track was last played in too distant past to be scrobbled.", "%1 tracks were last played in too distant past to be scrobbled.", scrobbleErrorCounts[ ScrobblingService::FromTheDistantPast ] ); if( scrobbleErrorCounts.value( ScrobblingService::SkippedByUser ) ) text << i18np( "Scrobbling of one track was skipped as configured by the user.", "Scrobbling of %1 tracks was skipped as configured by the user.", scrobbleErrorCounts[ ScrobblingService::SkippedByUser ] ); Amarok::Components::logger()->longMessage( text.join( "
\n" ) ); } void Process::slotSaveSizeAndDelete() { if( m_dialog ) { KConfigGroup group = Amarok::config( "StatSyncingDialog" ); group.writeEntry( "geometry", m_dialog->saveGeometry() ); } deleteLater(); } void Process::slotDeleteDialog() { // we cannot use deleteLater(), we don't have spare eventloop iteration delete m_dialog.data(); } diff --git a/src/statusbar/CompoundProgressBar.cpp b/src/statusbar/CompoundProgressBar.cpp index 011a9802d8..70a54aae42 100644 --- a/src/statusbar/CompoundProgressBar.cpp +++ b/src/statusbar/CompoundProgressBar.cpp @@ -1,272 +1,260 @@ /**************************************************************************************** * 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 . * ****************************************************************************************/ #include "CompoundProgressBar.h" #include "core/support/Debug.h" #include #include #include CompoundProgressBar::CompoundProgressBar( QWidget *parent ) : ProgressBar( parent ) , m_mutex( QMutex::Recursive ) { - m_progressDetailsWidget = new PopupWidget( parent ); + m_progressDetailsWidget = new PopupWidget(); m_progressDetailsWidget->hide(); connect( cancelButton(), &QAbstractButton::clicked, this, &CompoundProgressBar::cancelAll ); } CompoundProgressBar::~CompoundProgressBar() { delete m_progressDetailsWidget; m_progressDetailsWidget = 0; } void CompoundProgressBar::addProgressBar( ProgressBar *childBar, QObject *owner ) { QMutexLocker locker( &m_mutex ); m_progressMap.insert( owner, childBar ); m_progressDetailsWidget->layout()->addWidget( childBar ); if( m_progressDetailsWidget->width() < childBar->width() ) m_progressDetailsWidget->setMinimumWidth( childBar->width() ); m_progressDetailsWidget->setMinimumHeight( childBar->height() * m_progressMap.count() + 8 ); m_progressDetailsWidget->reposition(); connect( childBar, &ProgressBar::percentageChanged, this, &CompoundProgressBar::childPercentageChanged ); connect( childBar, &ProgressBar::cancelled, this, &CompoundProgressBar::childBarCancelled ); connect( childBar, &ProgressBar::complete, this, &CompoundProgressBar::childBarComplete ); connect( owner, &QObject::destroyed, this, &CompoundProgressBar::slotObjectDestroyed ); if( m_progressMap.count() == 1 ) { setDescription( childBar->descriptionLabel()->text() ); cancelButton()->setToolTip( i18n( "Abort" ) ); } else { setDescription( i18n( "Multiple background tasks running (click to show)" ) ); cancelButton()->setToolTip( i18n( "Abort all background tasks" ) ); } cancelButton()->setHidden( false ); } void CompoundProgressBar::endProgressOperation( QObject *owner ) { QMutexLocker locker( &m_mutex ); if( !m_progressMap.contains( owner ) ) return ; childBarComplete( m_progressMap.value( owner ) ); } void CompoundProgressBar::slotIncrementProgress() { incrementProgress( sender() ); } void CompoundProgressBar::incrementProgress( const QObject *owner ) { QMutexLocker locker( &m_mutex ); if( !m_progressMap.contains( owner ) ) return ; m_progressMap.value( owner )->setValue( m_progressMap.value( owner )->value() + 1 ); } void CompoundProgressBar::setProgress( const QObject *owner, int steps ) { QMutexLocker locker( &m_mutex ); if( !m_progressMap.contains( owner ) ) return ; m_progressMap.value( owner )->setValue( steps ); } void CompoundProgressBar::mousePressEvent( QMouseEvent *event ) { QMutexLocker locker( &m_mutex ); if( m_progressDetailsWidget->isHidden() ) { if( m_progressMap.count() ) showDetails(); } else { hideDetails(); } event->accept(); } void CompoundProgressBar::setProgressTotalSteps( const QObject *owner, int value ) { QMutexLocker locker( &m_mutex ); if( !m_progressMap.contains( owner ) ) return ; m_progressMap.value( owner )->setMaximum( value ); } -void CompoundProgressBar::setParent( QWidget *parent ) -{ - QMutexLocker locker( &m_mutex ); - - delete m_progressDetailsWidget; - m_progressDetailsWidget = new PopupWidget( parent ); - m_progressDetailsWidget->hide(); - - ProgressBar::setParent( parent ); -} - - void CompoundProgressBar::setProgressStatus( const QObject *owner, const QString &text ) { QMutexLocker locker( &m_mutex ); if( !m_progressMap.contains( owner ) ) return ; m_progressMap.value( owner )->setDescription( text ); } void CompoundProgressBar::childPercentageChanged() { progressBar()->setValue( calcCompoundPercentage() ); } void CompoundProgressBar::childBarCancelled( ProgressBar *childBar ) { childBarFinished( childBar ); } void CompoundProgressBar::childBarComplete( ProgressBar *childBar ) { childBarFinished( childBar ); } void CompoundProgressBar::slotObjectDestroyed( QObject *object ) { QMutexLocker locker( &m_mutex ); if( m_progressMap.contains( object ) ) { childBarFinished( m_progressMap.value( object ) ); } } void CompoundProgressBar::childBarFinished( ProgressBar *bar ) { QMutexLocker locker( &m_mutex ); QObject *owner = const_cast( m_progressMap.key( bar ) ); owner->disconnect( this ); owner->disconnect( bar ); m_progressMap.remove( owner ); m_progressDetailsWidget->layout()->removeWidget( bar ); m_progressDetailsWidget->setFixedHeight( bar->height() * m_progressMap.count() + 8 ); m_progressDetailsWidget->reposition(); delete bar; if( m_progressMap.count() == 1 ) { //only one job still running, so no need to use the details widget any more. //Also set the text to the description of //the job instead of the "Multiple background tasks running" text. setDescription( m_progressMap.values().at( 0 )->descriptionLabel()->text() ); cancelButton()->setToolTip( i18n( "Abort" ) ); hideDetails(); } else if( m_progressMap.empty() ) { progressBar()->setValue( 0 ); hideDetails(); emit( allDone() ); return; } else { setDescription( i18n( "Multiple background tasks running (click to show)" ) ); cancelButton()->setToolTip( i18n( "Abort all background tasks" ) ); } progressBar()->setValue( calcCompoundPercentage() ); } int CompoundProgressBar::calcCompoundPercentage() { QMutexLocker locker( &m_mutex ); int count = m_progressMap.count(); int total = 0; foreach( ProgressBar *currentBar, m_progressMap ) total += currentBar->percentage(); return count == 0 ? 0 : total / count; } void CompoundProgressBar::cancelAll() { QMutexLocker locker( &m_mutex ); foreach( ProgressBar *currentBar, m_progressMap ) currentBar->cancel(); } void CompoundProgressBar::showDetails() { QMutexLocker locker( &m_mutex ); m_progressDetailsWidget->raise(); //Hack to make sure it has the right height first time it is shown... m_progressDetailsWidget->setFixedHeight( m_progressMap.values().at( 0 )->height() * m_progressMap.count() + 8 ); m_progressDetailsWidget->reposition(); m_progressDetailsWidget->show(); } void CompoundProgressBar::hideDetails() { m_progressDetailsWidget->hide(); } void CompoundProgressBar::toggleDetails() { if( m_progressDetailsWidget->isVisible() ) hideDetails(); else showDetails(); } diff --git a/src/statusbar/CompoundProgressBar.h b/src/statusbar/CompoundProgressBar.h index 168d991ec9..dedf59f167 100644 --- a/src/statusbar/CompoundProgressBar.h +++ b/src/statusbar/CompoundProgressBar.h @@ -1,84 +1,81 @@ /**************************************************************************************** * 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 . * ****************************************************************************************/ #ifndef COMPOUNDPROGRESSBAR_H #define COMPOUNDPROGRESSBAR_H #include "statusbar/ProgressBar.h" #include "statusbar/PopupWidget.h" #include #include #include #include /** * A progress bar that wraps a number of simple progress bars and displays their * overall progress. Also features an expanded mode that allows the user to view * and canceld individual operations */ class AMAROK_EXPORT CompoundProgressBar : public ProgressBar { Q_OBJECT public: explicit CompoundProgressBar( QWidget *parent ); ~CompoundProgressBar(); void addProgressBar( ProgressBar *progressBar, QObject *owner ); void incrementProgress( const QObject *owner ); void setProgressTotalSteps( const QObject *owner, int value ); void setProgressStatus( const QObject *owner, const QString &text ); void setProgress( const QObject *owner, int steps ); - /* reimplemented from QWidget for correct positioning of progressDetailsWidget */ - virtual void setParent( QWidget *parent ); - /* reimplemented from QWidget to open/close the details widget */ virtual void mousePressEvent( QMouseEvent *event ); public Q_SLOTS: void endProgressOperation( QObject *owner ); void slotIncrementProgress(); Q_SIGNALS: void allDone(); protected Q_SLOTS: void cancelAll(); void toggleDetails(); void childPercentageChanged( ); void childBarCancelled( ProgressBar *progressBar ); void childBarComplete( ProgressBar *progressBar ); void slotObjectDestroyed( QObject *object ); private: void showDetails(); void hideDetails(); void childBarFinished( ProgressBar *bar ); int calcCompoundPercentage(); QMap< const QObject *, ProgressBar *> m_progressMap; PopupWidget *m_progressDetailsWidget; QMutex m_mutex; // protecting m_progressMap consistency }; #endif diff --git a/src/statusbar/LongMessageWidget.cpp b/src/statusbar/LongMessageWidget.cpp index 460ea5c5b9..7b056b60ed 100644 --- a/src/statusbar/LongMessageWidget.cpp +++ b/src/statusbar/LongMessageWidget.cpp @@ -1,148 +1,145 @@ /**************************************************************************************** * Copyright (c) 2008 Nikolaj Hald Nielsen * * Copyright (c) 2005 Max Howell * * * * This program is free software; you can redistribute it and/or modify it under * * the 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 "LongMessageWidget.h" #include "core/support/Debug.h" #include #include #include #include #include -LongMessageWidget::LongMessageWidget( QWidget *anchor, const QString &message, - Amarok::Logger::MessageType type ) - : PopupWidget( anchor ) - , m_counter( 0 ) +LongMessageWidget::LongMessageWidget( const QString &message ) + : m_counter( 0 ) , m_timeout( 6000 ) { DEBUG_BLOCK - Q_UNUSED( type ) setFrameStyle( QFrame::StyledPanel | QFrame::Raised ); setContentsMargins( 4, 4, 4, 4 ); setMinimumWidth( 26 ); setMinimumHeight( 26 ); setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding ); QPalette p = QToolTip::palette(); setPalette( p ); BoxWidget *hbox = new BoxWidget( false, this ); hbox->layout()->setSpacing( 12 ); m_countdownFrame = new CountdownFrame( hbox ); m_countdownFrame->setObjectName( "counterVisual" ); m_countdownFrame->setFixedWidth( fontMetrics().width( "X" ) ); m_countdownFrame->setFrameStyle( QFrame::Plain | QFrame::Box ); QPalette pal; pal.setColor( m_countdownFrame->foregroundRole(), p.dark().color() ); m_countdownFrame->setPalette( pal ); QLabel *alabel = new QLabel( message, hbox ); alabel->setWordWrap( true ); alabel->setOpenExternalLinks( true ); alabel->setObjectName( "label" ); alabel->setTextFormat( Qt::RichText ); alabel->setTextInteractionFlags( Qt::TextBrowserInteraction ); alabel->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred ); alabel->setPalette( p ); hbox = new BoxWidget( false, this ); QPushButton *button = new QPushButton( QStringLiteral( "closeButton" ), hbox ); connect( button, &QAbstractButton::clicked, this, &LongMessageWidget::close ); reposition(); show(); m_timerId = startTimer( m_timeout / m_countdownFrame->height() ); } LongMessageWidget::~LongMessageWidget() {} void LongMessageWidget::close() { hide(); emit( closed() ); } void LongMessageWidget::timerEvent( QTimerEvent* ) { if( !m_timeout ) { killTimer( m_timerId ); return; } CountdownFrame *&h = m_countdownFrame; if( m_counter < h->height() - 3 ) { h->setFilledRatio(( float ) m_counter / ( float ) h->height() ); h->repaint(); } if( !testAttribute( Qt::WA_UnderMouse ) ) m_counter++; if( m_counter > h->height() ) { killTimer( m_timerId ); h->setFilledRatio( 1 ); h->repaint(); close(); } else { killTimer( m_timerId ); m_timerId = startTimer( m_timeout / h->height() ); } } ////////////////////////////////////////////////////////////////////////////// // class CountdownFrame ////////////////////////////////////////////////////////////////////////////// CountdownFrame::CountdownFrame( QWidget *parent ) : QFrame( parent ) , m_filled( 0.0 ) {} void CountdownFrame::setFilledRatio( float filled ) { m_filled = filled; } void CountdownFrame::paintEvent( QPaintEvent *e ) { QFrame::paintEvent( e ); QPalette p = palette(); p.setCurrentColorGroup( QPalette::Active ); QPainter( this ).fillRect( 2, m_filled * height() , width() - 4, height() - ( m_filled * height() ) , p.highlight() ); } diff --git a/src/statusbar/LongMessageWidget.h b/src/statusbar/LongMessageWidget.h index e4c81e4035..84f02efe25 100644 --- a/src/statusbar/LongMessageWidget.h +++ b/src/statusbar/LongMessageWidget.h @@ -1,63 +1,63 @@ /**************************************************************************************** * Copyright (c) 2008 Nikolaj Hald Nielsen * * Copyright (c) 2005 Max Howell * * * * This program is free software; you can redistribute it and/or modify it under * * the 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 LONGMESSAGEWIDGET_H #define LONGMESSAGEWIDGET_H #include "PopupWidget.h" #include "core/interfaces/Logger.h" class CountdownFrame : public QFrame { public: explicit CountdownFrame( QWidget *parent = 0 ); void setFilledRatio( float filled ); // 0 to 1 virtual void paintEvent( QPaintEvent *e ); protected: float m_filled; }; /** A widget for displaying a long message as an overlay */ class LongMessageWidget : public PopupWidget { Q_OBJECT public: - LongMessageWidget( QWidget *anchor, const QString &message, Amarok::Logger::MessageType type ); + LongMessageWidget( const QString &message ); ~LongMessageWidget(); Q_SIGNALS: void closed(); protected: void timerEvent( QTimerEvent * ); private Q_SLOTS: void close(); private: CountdownFrame *m_countdownFrame; int m_counter; int m_timeout; int m_timerId; }; #endif diff --git a/src/statusbar/PopupWidget.cpp b/src/statusbar/PopupWidget.cpp index 9b458f2649..c22e2e7434 100644 --- a/src/statusbar/PopupWidget.cpp +++ b/src/statusbar/PopupWidget.cpp @@ -1,60 +1,60 @@ /**************************************************************************************** * 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 . * ****************************************************************************************/ #include "PopupWidget.h" #include "MainWindow.h" #include "core/support/Debug.h" #include -PopupWidget::PopupWidget( QWidget *anchor, const QString &name ) - : BoxWidget( true, The::mainWindow() ) +PopupWidget::PopupWidget( const QString &name ) + : BoxWidget( true ) { Q_UNUSED( name ); - Q_UNUSED( anchor ); setBackgroundRole( QPalette::Window ); setAutoFillBackground( true ); setFrameStyle( QFrame::Box ); setMinimumWidth( 26 ); setMinimumHeight( 26 ); setContentsMargins( 4, 4, 4, 4 ); setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ); - - reposition(); } PopupWidget::~PopupWidget() { DEBUG_BLOCK } void PopupWidget::reposition() { adjustSize(); + if( !The::mainWindow() ) + return; + //HACK: put longmessage popup in the bottom right of the window. QPoint p; p.setX( The::mainWindow()->width() - width() ); p.setY( The::mainWindow()->height() - height() ); move( p ); } diff --git a/src/statusbar/PopupWidget.h b/src/statusbar/PopupWidget.h index 1a77042e73..f85ffc86e7 100644 --- a/src/statusbar/PopupWidget.h +++ b/src/statusbar/PopupWidget.h @@ -1,31 +1,31 @@ /**************************************************************************************** * 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 . * ****************************************************************************************/ #ifndef POPUPWIDGET_H #define POPUPWIDGET_H #include "widgets/BoxWidget.h" class PopupWidget : public BoxWidget { public: - explicit PopupWidget( QWidget *anchor, const QString &name = QString() ); + explicit PopupWidget( const QString &name = QString() ); ~PopupWidget(); void reposition(); }; #endif diff --git a/src/statusbar/ProgressBar.cpp b/src/statusbar/ProgressBar.cpp index f20d54be7b..ca334e6ab8 100644 --- a/src/statusbar/ProgressBar.cpp +++ b/src/statusbar/ProgressBar.cpp @@ -1,123 +1,112 @@ /**************************************************************************************** * 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 . * ****************************************************************************************/ #include "statusbar/ProgressBar.h" #include "core/support/Debug.h" #include "MainWindow.h" #include #include #include ProgressBar::ProgressBar( QWidget *parent ) : QFrame( parent ) { setFixedHeight( 30 ); setContentsMargins( 0, 0, 0, 4 ); QVBoxLayout *box = new QVBoxLayout; box->setMargin( 0 ); box->setSpacing( 3 ); QHBoxLayout *descriptionLayout = new QHBoxLayout; descriptionLayout->setMargin( 0 ); descriptionLayout->setSpacing( 2 ); m_descriptionLabel = new QLabel; m_descriptionLabel->setWordWrap( true ); //add with stretchfactor 1 so it takes up more space then the cancel button descriptionLayout->addWidget( m_descriptionLabel, 1 ); m_cancelButton = new QToolButton; m_cancelButton->setIcon( QIcon::fromTheme( "dialog-cancel-amarok" ) ); m_cancelButton->setToolTip( i18n( "Abort" ) ); m_cancelButton->setHidden( true ); m_cancelButton->setFixedWidth( 16 ); m_cancelButton->setFixedHeight( 16 ); m_cancelButton->setAutoFillBackground( false ); m_cancelButton->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); descriptionLayout->addWidget( m_cancelButton ); descriptionLayout->setAlignment( m_cancelButton, Qt::AlignRight ); box->addLayout( descriptionLayout ); m_progressBar = new QProgressBar; m_progressBar->setMinimum( 0 ); m_progressBar->setMaximum( 100 ); m_progressBar->setFixedHeight( 5 ); m_progressBar->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed ); m_progressBar->setTextVisible( false ); box->addWidget( m_progressBar ); box->setAlignment( m_progressBar, Qt::AlignBottom ); setLayout( box ); } ProgressBar::~ProgressBar() { } void ProgressBar::setDescription( const QString &description ) { m_descriptionLabel->setText( description ); } -ProgressBar * -ProgressBar::setAbortSlot( QObject *receiver, const char *slot, Qt::ConnectionType type ) -{ - cancelButton()->setHidden( false ); - if( receiver ) - connect( this, SIGNAL(cancelled(ProgressBar*)), receiver, slot, type ); - connect( cancelButton(), &QAbstractButton::clicked, this, &ProgressBar::cancel ); - - return this; -} - void ProgressBar::cancel() { DEBUG_BLOCK debug() << "cancelling operation: " << m_descriptionLabel->text(); emit( cancelled( this ) ); } void ProgressBar::setValue( int percentage ) { progressBar()->setValue( percentage ); emit( percentageChanged( percentage ) ); //this safety check has to be removed as KJobs sometimes start out //by showing 100%, thus removing the progress info before it even gets started /*if ( percentage == m_progressBar->maximum() ) QTimer::singleShot( POST_COMPLETION_DELAY, this, SLOT(delayedDone()) );*/ } void ProgressBar::delayedDone() { emit( complete( this ) ); } int ProgressBar::percentage() { if( m_progressBar->maximum() == 100 ) return m_progressBar->value(); return (int)( ( (float) m_progressBar->value() / (float)m_progressBar->maximum() ) * 100.0 ); } diff --git a/src/statusbar/ProgressBar.h b/src/statusbar/ProgressBar.h index 0d2213a454..f626ad15c1 100644 --- a/src/statusbar/ProgressBar.h +++ b/src/statusbar/ProgressBar.h @@ -1,83 +1,83 @@ /**************************************************************************************** * 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 . * ****************************************************************************************/ #ifndef PROGRESSBAR_H #define PROGRESSBAR_H #include "amarok_export.h" #include #include #include #include #include +#include + #define POST_COMPLETION_DELAY 2000 /** * A widget that encapsulates a progress bar, a description string and a cancel button. */ class AMAROK_EXPORT ProgressBar : public QFrame { Q_OBJECT public: explicit ProgressBar( QWidget *parent ); ~ProgressBar(); void setDescription( const QString &description ); - ProgressBar *setAbortSlot( QObject *receiver, const char *slot, - Qt::ConnectionType type = Qt::AutoConnection ); - template - ProgressBar *setAbortSlot( const typename QtPrivate::FunctionPointer::Object *receiver, Func slot, + template + ProgressBar *setAbortSlot( Receiver receiver, Func slot, Qt::ConnectionType type = Qt::AutoConnection ) { cancelButton()->setHidden( false ); if( receiver ) connect( this, &ProgressBar::cancelled, receiver, slot, type ); connect( cancelButton(), &QAbstractButton::clicked, this, &ProgressBar::cancel ); return this; } QToolButton *cancelButton() { return m_cancelButton; } QProgressBar *progressBar() { return m_progressBar; } QLabel *descriptionLabel() { return m_descriptionLabel; } int maximum() { return m_progressBar->maximum(); } void setMaximum( int max ) { m_progressBar->setMaximum( max ); } int value() { return m_progressBar->value(); } void setValue( int value ); int percentage(); public Q_SLOTS: void cancel(); void delayedDone(); void slotTotalSteps( int steps ) { m_progressBar->setMaximum( steps ); } Q_SIGNALS: void cancelled( ProgressBar * ); void complete( ProgressBar * ); void percentageChanged( int ); private: QToolButton *m_cancelButton; QProgressBar *m_progressBar; QLabel *m_descriptionLabel; }; #endif diff --git a/src/widgets/MetaQueryWidget.h b/src/widgets/MetaQueryWidget.h index 993d35a1f7..da17ed7e5b 100644 --- a/src/widgets/MetaQueryWidget.h +++ b/src/widgets/MetaQueryWidget.h @@ -1,231 +1,231 @@ /**************************************************************************************** * Copyright (c) 2008 Daniel Caleb Jones * * Copyright (c) 2009 Mark Kretschmann * * 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) 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_METAQUERY_H #define AMAROK_METAQUERY_H #include #include #include "core/meta/forward_declarations.h" #include "core/meta/support/MetaConstants.h" #include #include class QFrame; class QGridLayout; class QHBoxLayout; class QLabel; class QToolButton; class QVBoxLayout; class KToolBar; namespace Collections { class QueryMaker; } /** * A class that allows to select a time distance. */ class TimeDistanceWidget : public QWidget { Q_OBJECT public: explicit TimeDistanceWidget( QWidget *parent = 0 ); qint64 timeDistance() const; void setTimeDistance( qint64 value ); - template - void connectChanged( typename QtPrivate::FunctionPointer::Object *receiver, Func slot ) + template + void connectChanged( Receiver receiver, Func slot ) { connect( m_timeEdit, QOverload::of(&QSpinBox::valueChanged), receiver, slot ); connect( m_unitSelection, QOverload::of(&QComboBox::currentIndexChanged), receiver, slot ); } protected: QSpinBox *m_timeEdit; QComboBox *m_unitSelection; private Q_SLOTS: void slotUpdateComboBoxLabels( int value ); }; class MetaQueryWidget : public QWidget { Q_PROPERTY( bool hideFieldSelector READ isFieldSelectorHidden WRITE setFieldSelectorHidden ) Q_OBJECT public: /** Creates a MetaQueryWidget which can be used to select one meta query filter. * @param onlyNumeric If set to true the widget will only display numeric fields. * @param noCondition If set to true no condition can be selected. */ explicit MetaQueryWidget( QWidget* parent = 0, bool onlyNumeric = false, bool noCondition = false ); ~MetaQueryWidget(); enum FilterCondition { Equals = 0, GreaterThan = 1, LessThan = 2, Between = 3, OlderThan = 4, NewerThan = 5, Contains = 6 }; struct Filter { Filter() : m_field( 0 ) , numValue( 0 ) , numValue2( 0 ) , condition( Contains ) {} qint64 field() const { return m_field; } void setField( qint64 newField ); /** Returns a textual representation of the field. */ QString fieldToString() const; /** Returns a textual representation of the filter. * Used for the edit filter dialog (or for debugging) */ QString toString( bool invert = false ) const; bool isNumeric() const { return MetaQueryWidget::isNumeric( m_field ); } bool isDate() const { return MetaQueryWidget::isDate( m_field ); } /** Returns the minimum allowed value for the field type */ static qint64 minimumValue( quint64 field ); static qint64 maximumValue( quint64 field ); static qint64 defaultValue( quint64 field ); private: qint64 m_field; public: QString value; qint64 numValue; qint64 numValue2; FilterCondition condition; }; /** Returns the current filter value. */ Filter filter() const; /** Returns true if the given field is a numeric field */ static bool isNumeric( qint64 field ); /** Returns true if the given field is a date field */ static bool isDate( qint64 field ); /** Returns a localized text of the condition. @param field Needed in order to test whether the field is a date, numeric or a string since the texts differ slightly */ static QString conditionToString( FilterCondition condition, qint64 field ); public Q_SLOTS: void setFilter(const MetaQueryWidget::Filter &value); void setField( const qint64 field ); /** Field Selector combo box visibility state */ bool isFieldSelectorHidden() const; void setFieldSelectorHidden( const bool hidden ); Q_SIGNALS: void changed(const MetaQueryWidget::Filter &value); private Q_SLOTS: void fieldChanged( int ); void compareChanged( int ); void valueChanged( const QString& ); void numValueChanged( int ); void numValue2Changed( int ); void numValueChanged( qint64 ); void numValue2Changed( qint64 ); void numValueChanged( const QTime& ); void numValue2Changed( const QTime& ); void numValueDateChanged(); void numValue2DateChanged(); void numValueTimeDistanceChanged(); void numValueFormatChanged( int ); void populateComboBox( QStringList ); void comboBoxPopulated(); private: void makeFieldSelection(); /** Adds the value selection widgets to the layout. * Adds m_compareSelection, m_valueSelection1, m_valueSelection2 to the layout. */ void setValueSelection(); void makeCompareSelection(); void makeValueSelection(); void makeGenericComboSelection( bool editable, Collections::QueryMaker* populateQuery ); void makeMetaComboSelection( qint64 field ); void makeFormatComboSelection(); void makeGenericNumberSelection( qint64 field, const QString& unit = "" ); void makePlaycountSelection(); void makeRatingSelection(); void makeLengthSelection(); void makeDateTimeSelection(); void makeFilenameSelection(); bool m_onlyNumeric; bool m_noCondition; bool m_settingFilter; // if set to true we are just setting the filter QVBoxLayout* m_layoutMain; QHBoxLayout* m_layoutValue; QVBoxLayout* m_layoutValueLabels; QVBoxLayout* m_layoutValueValues; QComboBox* m_fieldSelection; QLabel* m_andLabel; QComboBox* m_compareSelection; QWidget* m_valueSelection1; QWidget* m_valueSelection2; Filter m_filter; QMap< QObject*, QPointer > m_runningQueries; }; #endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index fe60459e44..6f01a61035 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,165 +1,152 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) find_package(Googlemock REQUIRED) set_package_properties( GOOGLEMOCK PROPERTIES DESCRIPTION "Used in Amarok's tests." URL "http://code.google.com/p/googlemock/" TYPE REQUIRED ) set( AMAROK_SOURCE_TREE ${CMAKE_SOURCE_DIR}/src ) set( AMAROK_TEST_TREE ${CMAKE_SOURCE_DIR}/tests ) set( AMAROK_UTILITY_TREE ${CMAKE_SOURCE_DIR}/utilities ) set( AMAROK_UTILITIES_DIR ${CMAKE_BINARY_DIR}/utilities ) set( STRESS_TEST_TRACK_COUNT 20000 ) configure_file(config-amarok-test.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-amarok-test.h ) include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/src ${CMAKE_BINARY_DIR}/src ${GOOGLEMOCK_INCLUDE_DIR} ) add_subdirectory( amarokurls ) add_subdirectory( browsers ) add_subdirectory( context ) add_subdirectory( core ) add_subdirectory( core-impl ) add_subdirectory( dynamic ) if( ${CMAKE_SYSTEM_NAME} MATCHES "Linux" ) add_subdirectory( importers ) endif() add_subdirectory( playlist ) add_subdirectory( playlistmanager ) add_subdirectory( timecode ) add_subdirectory( scanner ) add_subdirectory( synchronization ) #------------------------ Test Amarok namespace ----------------------------- set( testamarok_SRCS TestAmarok.cpp ${AMAROK_SOURCE_TREE}/core/support/Amarok.cpp ) add_executable( testamarok ${testamarok_SRCS} ) add_test(NAME testamarok COMMAND $) ecm_mark_as_test(testamarok) target_link_libraries( testamarok KF5::KIOCore Qt5::Test amarokcore amaroklib ) #------------------------ Test CaseConverter ----------------------------- set( testcaseconverter_SRCS TestCaseConverter.cpp ${AMAROK_SOURCE_TREE}/CaseConverter.cpp ) add_executable( testcaseconverter ${testcaseconverter_SRCS} ) add_test(NAME testcaseconverter COMMAND $) ecm_mark_as_test(testcaseconverter) target_link_libraries(testcaseconverter Qt5::Test amarokcore) #------------------------ Test Debug ----------------------------- add_definitions(-DDEBUG_OVERRIDE_ELAPSED_TIME=4.9) set( testdebug_SRCS TestDebug.cpp ${AMAROK_SOURCE_TREE}/core/support/Debug.cpp ) add_executable( testdebug ${testdebug_SRCS} ) add_test(NAME testdebug COMMAND $) ecm_mark_as_test(testdebug) target_link_libraries(testdebug Qt5::Test amarokcore ) #------------------------ Test EngineController ----------------------------- set( testenginecontroller_SRCS TestEngineController.cpp ) add_executable( testenginecontroller ${testenginecontroller_SRCS} ) add_test(NAME testenginecontroller COMMAND $) ecm_mark_as_test(testenginecontroller) target_link_libraries( testenginecontroller KF5::ThreadWeaver Qt5::Test amaroklib amarokcore ) #------------------------ Test Expression ----------------------------- set( testexpression_SRCS TestExpression.cpp ${AMAROK_SOURCE_TREE}/core-impl/collections/support/Expression.cpp ) add_executable( testexpression ${testexpression_SRCS} ) add_test(NAME testexpression COMMAND $) ecm_mark_as_test(testexpression) target_link_libraries(testexpression Qt5::Test) #------------------------ Test QStringx ----------------------------- set( testqstringx_SRCS TestQStringx.cpp ${AMAROK_SOURCE_TREE}/QStringx.cpp ) add_executable( testqstringx ${testqstringx_SRCS} ) add_test(NAME testqstringx COMMAND $) ecm_mark_as_test(testqstringx) target_link_libraries(testqstringx Qt5::Test) -#------------------------ Test SmartPointerList ----------------------------- - -set( testsmartpointerlist_SRCS - TestSmartPointerList.cpp - ${AMAROK_SOURCE_TREE}/core/support/SmartPointerList.cpp - ) - -add_executable( testsmartpointerlist ${testsmartpointerlist_SRCS} ) -add_test(NAME testsmartpointerlist COMMAND $) -ecm_mark_as_test(testsmartpointerlist) - -target_link_libraries(testsmartpointerlist Qt5::Test) - #------------------------ Test TagGuesser ----------------------------- set( testtagguesser_SRCS TestTagGuesser.cpp ${CMAKE_SOURCE_DIR}/shared/TagsFromFileNameGuesser.cpp ${AMAROK_SOURCE_TREE}/dialogs/TagGuesser.cpp ) add_executable( testtagguesser ${testtagguesser_SRCS} ) add_test(NAME testtagguesser COMMAND $) ecm_mark_as_test(testtagguesser) target_link_libraries(testtagguesser Qt5::Test amarokcore) #------------------------ Test TrackOrganizer ----------------------------- set( testtrackorganizer_SRCS TestTrackOrganizer.cpp ${AMAROK_SOURCE_TREE}/dialogs/TrackOrganizer.cpp ${AMAROK_SOURCE_TREE}/core/meta/Meta.cpp ${AMAROK_SOURCE_TREE}/QStringx.cpp ${GOOGLEMOCK_SRCS} ) add_executable( testtrackorganizer ${testtrackorganizer_SRCS} ) add_test(NAME testtrackorganizer COMMAND $) ecm_mark_as_test(testtrackorganizer) # Since Google recommends not to distribute a pre-compiled gtest library # we have to build it our own if(GOOGLEMOCK_GTEST_SOURCES) add_subdirectory( ${GOOGLEMOCK_GTEST_SOURCES} gtest ) add_dependencies( testtrackorganizer gtest ) endif() add_dependencies( testtrackorganizer amarokcore ) add_dependencies( testtrackorganizer amaroklib ) target_link_libraries( testtrackorganizer amarokcore amaroklib ${KDE4_SOLID_LIBRARY} Qt5::Test Qt5::Core Qt5::Gui ${GOOGLEMOCK_LIBRARIES}) diff --git a/tests/TestSmartPointerList.cpp b/tests/TestSmartPointerList.cpp deleted file mode 100644 index 2a27dda698..0000000000 --- a/tests/TestSmartPointerList.cpp +++ /dev/null @@ -1,135 +0,0 @@ -/*************************************************************************** - * Copyright (c) 2009 Max Howell * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the 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 "TestSmartPointerList.h" - -#include "core/support/SmartPointerList.h" - -#include -#include -#include - -QTEST_GUILESS_MAIN( TestSmartPointerList ) - -// use a macro, as we don't want to test copy ctor early -#define THREE_TIMERS( x ) SmartPointerList x; x << new QTimer << new QTimer << new QTimer - -TestSmartPointerList::TestSmartPointerList() -{ -} - -void TestSmartPointerList::testCount() -{ - THREE_TIMERS( objects ); - QCOMPARE( objects.count(), 3 ); -} - -void TestSmartPointerList::testCopy() -{ - THREE_TIMERS( objects1 ); - SmartPointerList objects2 = objects1; - - for (int x = 0; x < 3; ++x) - QVERIFY( objects1[x] == objects2[x] ); - - QCOMPARE( objects1.count(), 3 ); - QCOMPARE( objects2.count(), 3 ); - delete objects1.last(); - QCOMPARE( objects1.count(), 2 ); - QCOMPARE( objects2.count(), 2 ); -} - -void TestSmartPointerList::testCopyAndThenDelete() -{ - THREE_TIMERS( os1 ); - SmartPointerList* os2 = new SmartPointerList( os1 ); - SmartPointerList os3( *os2 ); - - delete os2; - - QCOMPARE( os1.count(), 3 ); - QCOMPARE( os3.count(), 3 ); - - delete os1[1]; - - QCOMPARE( os1.count(), 2 ); - QCOMPARE( os3.count(), 2 ); -} - -void TestSmartPointerList::testRemove() -{ - THREE_TIMERS( objects ); - delete objects.last(); - QCOMPARE( objects.count(), 2 ); -} - -void TestSmartPointerList::testRemoveAt() -{ - THREE_TIMERS( os ); - QTimer* t = os[1]; - os.removeAt( 1 ); - os << t; - QCOMPARE( os.count(), 3 ); - delete t; - QCOMPARE( os.count(), 2 ); -} - -void TestSmartPointerList::testMultipleOrgasms() -{ - THREE_TIMERS( os ); - for (int x = 0; x < 10; ++x) - os << os.last(); - QCOMPARE( os.count(), 13 ); - delete os.last(); - QCOMPARE( os.count(), 2 ); -} - -void TestSmartPointerList::testForeach() -{ - THREE_TIMERS( objects ); - int x = 0; - foreach (QTimer* o, objects) { - (void) o; - x++; - } - QCOMPARE( x, 3 ); -} - -void TestSmartPointerList::testOperatorPlus() -{ - THREE_TIMERS( os1 ); - SmartPointerList os2 = os1; - - QCOMPARE( (os1 + os2).count(), 6 ); - delete os1.last(); - QCOMPARE( (os1 + os2).count(), 4 ); -} - -void TestSmartPointerList::testOperatorPlusEquals() -{ - THREE_TIMERS( os ); - os += os; - os += os; - QCOMPARE( os.count(), 12 ); - QTimer* t = os.takeLast(); - QCOMPARE( os.count(), 11 ); - delete t; - QCOMPARE( os.count(), 8 ); -} - diff --git a/tests/TestSmartPointerList.h b/tests/TestSmartPointerList.h deleted file mode 100644 index 83ddb84e6a..0000000000 --- a/tests/TestSmartPointerList.h +++ /dev/null @@ -1,45 +0,0 @@ -/*************************************************************************** - * Copyright (c) 2009 Max Howell * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the 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 TESTSMARTPOINTERLIST_H -#define TESTSMARTPOINTERLIST_H - -#include -#include - -class TestSmartPointerList : public QObject -{ -Q_OBJECT - -public: - TestSmartPointerList(); - -private Q_SLOTS: - void testCount(); - void testCopy(); - void testCopyAndThenDelete(); - void testRemove(); - void testRemoveAt(); - void testMultipleOrgasms(); - void testForeach(); - void testOperatorPlus(); - void testOperatorPlusEquals(); -}; - -#endif // TESTSMARTPOINTERLIST_H diff --git a/tests/core-impl/logger/TestProxyLogger.cpp b/tests/core-impl/logger/TestProxyLogger.cpp index f7173d86bd..4c4eb0dd26 100644 --- a/tests/core-impl/logger/TestProxyLogger.cpp +++ b/tests/core-impl/logger/TestProxyLogger.cpp @@ -1,209 +1,209 @@ /**************************************************************************************** * Copyright (c) 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 . * ****************************************************************************************/ #include "TestProxyLogger.h" #include "core/interfaces/Logger.h" #include "core-impl/logger/ProxyLogger.h" #include "mocks/MockLogger.h" #include #include #include #include QTEST_GUILESS_MAIN( TestProxyLogger ) using ::testing::Return; using ::testing::AnyNumber; using ::testing::_; using ::testing::Mock; static ProxyLogger *s_logger; class DummyJob : public KJob { public: virtual void start() {} }; TestProxyLogger::TestProxyLogger() { int argc = 1; char **argv = (char **) malloc(sizeof(char *)); argv[0] = strdup( QCoreApplication::applicationName().toLocal8Bit().data() ); ::testing::InitGoogleMock( &argc, argv ); delete[] argv; } void TestProxyLogger::init() { s_logger = 0; } void TestProxyLogger::cleanup() { delete s_logger; } class ProgressJob : public QObject, public ThreadWeaver::Job { Q_OBJECT public: ProgressJob() : deleteJob( false ), deleteObject( false ) {} void run(ThreadWeaver::JobPointer self, ThreadWeaver::Thread *thread) { Q_UNUSED(self); Q_UNUSED(thread); KJob *job = new DummyJob(); QObject *obj = new QObject(); - s_logger->newProgressOperation( job, QString( "foo" ), obj, "foo()" ); + s_logger->newProgressOperation( job, QString( "foo" ), obj ); if( deleteJob ) delete job; if( deleteObject ) delete obj; } bool deleteJob; bool deleteObject; protected: void defaultBegin(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread) { Q_EMIT started(self); ThreadWeaver::Job::defaultBegin(self, thread); } void defaultEnd(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread) { ThreadWeaver::Job::defaultEnd(self, thread); if (!self->success()) { Q_EMIT failed(self); } Q_EMIT done(self); } Q_SIGNALS: /** This signal is emitted when this job is being processed by a thread. */ void started(ThreadWeaver::JobPointer); /** This signal is emitted when the job has been finished (no matter if it succeeded or not). */ void done(ThreadWeaver::JobPointer); /** This job has failed. * This signal is emitted when success() returns false after the job is executed. */ void failed(ThreadWeaver::JobPointer); }; void TestProxyLogger::testDoNotForwardDeletedJob() { s_logger = new ProxyLogger(); Amarok::MockLogger *mock = new Amarok::MockLogger(); - EXPECT_CALL( *mock, newProgressOperation( An(), _, _, _, _ ) ).Times( 0 ); + EXPECT_CALL( *mock, newProgressOperationImpl( An(), _, _, _, _ ) ).Times( 0 ); s_logger->setLogger( mock ); ProgressJob *job = new ProgressJob(); job->deleteJob = true; ThreadWeaver::Queue::instance()->enqueue( QSharedPointer(job) ); QTest::qSleep( 10 ); //ensure that the job has time to run QTest::qWait( 20 ); //give the ProxyLogger-internal timer time to fire QVERIFY( Mock::VerifyAndClearExpectations( &mock ) ); delete mock; } void TestProxyLogger::testDoNotForwardDeletedSlot() { s_logger = new ProxyLogger(); Amarok::MockLogger *mock = new Amarok::MockLogger(); - EXPECT_CALL( *mock, newProgressOperation( An(), _, 0, 0, _ ) ).Times( 1 ).WillOnce( Return() ); + EXPECT_CALL( *mock, newProgressOperationImpl( An(), _, nullptr, _, _ ) ).Times( 1 ).WillOnce( Return() ); s_logger->setLogger( mock ); ProgressJob *job = new ProgressJob(); job->deleteObject = true; ThreadWeaver::Queue::instance()->enqueue( QSharedPointer(job) ); QTest::qSleep( 10 ); //ensure that the job has time to run QTest::qWait( 20 ); //give the ProxyLogger-internal timer time to fire QVERIFY( Mock::VerifyAndClearExpectations( &mock ) ); delete mock; } void TestProxyLogger::testForwardLongMessage() { s_logger = new ProxyLogger(); Amarok::MockLogger *mock = new Amarok::MockLogger(); EXPECT_CALL( *mock, longMessage( _, _ ) ).Times( 1 ).WillOnce( Return() ); s_logger->setLogger( mock ); s_logger->longMessage( "foo", Amarok::Logger::Information ); QTest::qWait( 20 ); QVERIFY( Mock::VerifyAndClearExpectations( &mock ) ); delete mock; } void TestProxyLogger::testForwardProgressOperation() { s_logger = new ProxyLogger(); Amarok::MockLogger *mock = new Amarok::MockLogger(); - EXPECT_CALL( *mock, newProgressOperation( An(), _, _, _, _ ) ).Times( 1 ).WillOnce( Return() ); + EXPECT_CALL( *mock, newProgressOperationImpl( An(), _, _, _, _ ) ).Times( 1 ).WillOnce( Return() ); s_logger->setLogger( mock ); s_logger->newProgressOperation( new DummyJob(), QString( "foo" ) ); QTest::qWait( 20 ); QVERIFY( Mock::VerifyAndClearExpectations( &mock ) ); delete mock; } void TestProxyLogger::testForwardShortMessage() { s_logger = new ProxyLogger(); Amarok::MockLogger *mock = new Amarok::MockLogger(); EXPECT_CALL( *mock, shortMessage( _ ) ).Times( 1 ).WillOnce( Return() ); s_logger->setLogger( mock ); s_logger->shortMessage( "foo" ); QTest::qWait( 20 ); QVERIFY( Mock::VerifyAndClearExpectations( &mock ) ); delete mock; } #include "TestProxyLogger.moc" diff --git a/tests/importers/ImporterMocks.cpp b/tests/importers/ImporterMocks.cpp index 5538011c0d..274735efd6 100644 --- a/tests/importers/ImporterMocks.cpp +++ b/tests/importers/ImporterMocks.cpp @@ -1,119 +1,120 @@ /**************************************************************************************** * 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 . * ****************************************************************************************/ #include "ImporterMocks.h" #include "EngineController.h" #include "core/support/Amarok.h" #include "core/support/Components.h" using namespace ::testing; MockProvider::MockProvider( const QVariantMap &config, StatSyncing::ImporterManager *manager ) : StatSyncing::ImporterProvider( config, manager ) { } QVariantMap MockProvider::config() const { return m_config; } StatSyncing::ImporterManager* MockProvider::manager() const { return m_manager; } MockManager::MockManager() { } StatSyncing::ProviderPtrMap MockManager::providers() { return m_providers; } StatSyncing::ImporterProviderPtr MockManager::concreteNewInstance( const QVariantMap &cfg ) { return StatSyncing::ImporterProviderPtr( new NiceMock( cfg, this ) ); } void MockManager::providerForgottenProxy( const QString &providerId ) { return StatSyncing::ImporterManager::slotProviderForgotten( providerId ); } MockController::MockController( QObject *parent ) : StatSyncing::Controller( parent ) { } void ImporterMocks::initTestCase() { DefaultValue::Set( QString() ); DefaultValue::Set( QIcon() ); DefaultValue::Set( KPluginInfo() ); m_engineController = new EngineController; Amarok::Components::setEngineController( m_engineController ); } void ImporterMocks::init() { m_mockManager = new NiceMock; ON_CALL( *m_mockManager, newInstance( _ ) ) .WillByDefault( Invoke( m_mockManager, &MockManager::concreteNewInstance ) ); ON_CALL( *m_mockManager, type() ).WillByDefault( Return( "randomType" ) ); QVariantMap cfg; cfg["uid"] = QString( "providerUid" ); m_mockProvider = new NiceMock( cfg, m_mockManager ); m_mockController = new NiceMock; Amarok::Components::setStatSyncingController( m_mockController ); } void ImporterMocks::cleanup() { delete m_mockProvider; m_mockProvider = 0; delete m_mockManager; m_mockManager = 0; Amarok::Components::setStatSyncingController( 0 ); delete m_mockController; m_mockController = 0; Amarok::config( "Importers" ).deleteGroup(); } void ImporterMocks::cleanupTestCase() { + Amarok::config( "StatSyncing" ).deleteGroup(); Amarok::Components::setEngineController( 0 ); delete m_engineController; } diff --git a/tests/mocks/MockLogger.h b/tests/mocks/MockLogger.h index 4e29434cbf..6fc5c4903a 100644 --- a/tests/mocks/MockLogger.h +++ b/tests/mocks/MockLogger.h @@ -1,54 +1,54 @@ /**************************************************************************************** * Copyright (c) 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 . * ****************************************************************************************/ #ifndef AMAROK_MOCKLOGGER_H #define AMAROK_MOCKLOGGER_H #include #include "core/interfaces/Logger.h" using ::testing::Return; using ::testing::An; using ::testing::_; namespace Amarok { class MockLogger : public Amarok::Logger { public: MockLogger() : Amarok::Logger() { ON_CALL( *this, shortMessage( _ ) ).WillByDefault( Return() ); ON_CALL( *this, longMessage( _, _ ) ).WillByDefault( Return() ); - ON_CALL( *this, newProgressOperation( An(), _, _, _, _ ) ).WillByDefault( Return() ); - ON_CALL( *this, newProgressOperation( An(), _, _, _, _ ) ).WillByDefault( Return() ); - ON_CALL( *this, newProgressOperation( An(), _, _, _, _, _ ) ).WillByDefault( Return() ); + ON_CALL( *this, newProgressOperationImpl( An(), _, _, _, _ ) ).WillByDefault( Return() ); + ON_CALL( *this, newProgressOperationImpl( An(), _, _, _, _ ) ).WillByDefault( Return() ); + ON_CALL( *this, newProgressOperationImpl( An(), _, _, _, _, _, _, _ ) ).WillByDefault( Return() ); } MOCK_METHOD1( shortMessage, void( const QString& ) ); MOCK_METHOD2( longMessage, void( const QString&, Amarok::Logger::MessageType ) ); - MOCK_METHOD5( newProgressOperation, void( KJob*, const QString&, QObject*, const char*, - Qt::ConnectionType ) ); - MOCK_METHOD5( newProgressOperation, void( QNetworkReply*, const QString&, QObject*, - const char*, Qt::ConnectionType ) ); - MOCK_METHOD6( newProgressOperation, void( QObject *, const QString&, int, QObject*, - const char*, Qt::ConnectionType ) ); + MOCK_METHOD5( newProgressOperationImpl, void( KJob*, const QString&, QObject*, const std::function&, + Qt::ConnectionType ) ); + MOCK_METHOD5( newProgressOperationImpl, void( QNetworkReply*, const QString&, QObject*, + const std::function&, Qt::ConnectionType ) ); + MOCK_METHOD8( newProgressOperationImpl, void( QObject *, const QMetaMethod &, const QMetaMethod &, const QString&, + int, QObject*, const std::function&, Qt::ConnectionType ) ); }; } #endif