diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1c2b2aa6a5..98062f90dd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,978 +1,984 @@ # Improves speed of string concatenation add_definitions(-DQT_USE_FAST_CONCATENATION) add_definitions(-DQT_USE_FAST_OPERATOR_PLUS) if(NOT MSVC) add_definitions(-DQT_STRICT_ITERATORS) endif() if(APPLE) set(mac_SRCS app_mac.cpp mac/GrowlInterface.cpp ) # Notification Center Appeared in 10.8, or Darwin 12 if( CMAKE_SYSTEM_VERSION VERSION_GREATER "11.9.9") list(APPEND mac_SRCS mac/MacSystemNotify.mm) add_definitions(-DHAVE_NOTIFICATION_CENTER) endif() include_directories ( services/lastfm/ ) set( MAC_FILES_DIR ${CMAKE_SOURCE_DIR}/src/mac ) endif() include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ) add_subdirectory( core ) add_subdirectory( core-impl/collections ) add_subdirectory( core-impl/storage/sql ) add_subdirectory( context ) add_subdirectory( services ) add_subdirectory( scripting/scripts ) add_subdirectory( aboutdialog/libattica-ocsclient ) add_subdirectory( transcoding ) add_subdirectory( kconf_update ) add_subdirectory( importers ) ##################################################################### # PROXYCOLLECTION ##################################################################### set(aggregatecollection_SRCS core-impl/collections/aggregate/AggregateCollection.cpp core-impl/collections/aggregate/AggregateMeta.cpp core-impl/collections/aggregate/AggregateQueryMaker.cpp ) ##################################################################### # MEDIADEVICEFRAMEWORK ##################################################################### set(libmediadeviceframework_SRCS core-impl/collections/mediadevicecollection/MediaDeviceCollection.cpp core-impl/collections/mediadevicecollection/MediaDeviceCollectionLocation.cpp core-impl/collections/mediadevicecollection/MediaDeviceMeta.cpp core-impl/collections/mediadevicecollection/MediaDeviceTrackEditor.cpp core-impl/collections/mediadevicecollection/handler/MediaDeviceHandler.cpp core-impl/collections/mediadevicecollection/handler/MediaDeviceHandlerCapability.cpp core-impl/collections/mediadevicecollection/handler/capabilities/ArtworkCapability.cpp core-impl/collections/mediadevicecollection/handler/capabilities/PlaylistCapability.cpp core-impl/collections/mediadevicecollection/handler/capabilities/PodcastCapability.cpp core-impl/collections/mediadevicecollection/handler/capabilities/ReadCapability.cpp core-impl/collections/mediadevicecollection/handler/capabilities/WriteCapability.cpp core-impl/collections/mediadevicecollection/playlist/MediaDevicePlaylist.cpp core-impl/collections/mediadevicecollection/playlist/MediaDeviceUserPlaylistProvider.cpp core-impl/collections/mediadevicecollection/podcast/MediaDevicePodcastProvider.cpp core-impl/collections/mediadevicecollection/support/ConnectionAssistant.cpp core-impl/collections/mediadevicecollection/support/MediaDeviceInfo.cpp ) ##################################################################### # SERVICEFRAMEWORK ##################################################################### set(libserviceframework_SRCS services/DynamicServiceQueryMaker.cpp services/InfoParserBase.cpp services/ServiceAlbumCoverDownloader.cpp services/ServiceBase.cpp services/ServiceCapabilities.cpp services/ServiceCollection.cpp services/ServiceCollectionLocation.cpp services/ServiceCollectionTreeView.cpp services/ServiceMetaBase.cpp services/ServicePluginManager.cpp services/ServiceSqlCollection.cpp services/ServiceSqlQueryMaker.cpp services/ServiceSqlRegistry.cpp ) ##################################################################### # SERVICEBROWSER ##################################################################### set(libservicebrowser_SRCS browsers/servicebrowser/ServiceBrowser.cpp ) ##################################################################### # AMAROKURL ##################################################################### set(libamarokurl_SRCS amarokurls/AmarokUrl.cpp amarokurls/AmarokUrlAction.cpp amarokurls/AmarokUrlHandler.cpp amarokurls/BookmarkCurrentButton.cpp amarokurls/ContextUrlGenerator.cpp amarokurls/ContextUrlRunner.cpp amarokurls/NavigationUrlRunner.cpp amarokurls/NavigationUrlGenerator.cpp amarokurls/PlayUrlRunner.cpp amarokurls/PlayUrlGenerator.cpp amarokurls/BookmarkManager.cpp amarokurls/BookmarkManagerWidget.cpp amarokurls/BookmarkGroup.cpp amarokurls/BookmarkModel.cpp amarokurls/BookmarkTreeView.cpp amarokurls/BookmarkMetaActions.cpp ) ##################################################################### # SCRIPTABLESERVICE ##################################################################### set(libscriptableservice_SRCS services/scriptable/ScriptableService.cpp services/scriptable/ScriptableServiceCollection.cpp services/scriptable/ScriptableServiceCollectionTreeModel.cpp services/scriptable/ScriptableServiceInfoParser.cpp services/scriptable/ScriptableServiceManager.cpp services/scriptable/ScriptableServiceMeta.cpp services/scriptable/ScriptableServiceQueryMaker.cpp ) ##################################################################### # CONFIGDIALOG ##################################################################### set(libconfigdialog_SRCS configdialog/ConfigDialog.cpp configdialog/ConfigDialogBase.cpp configdialog/dialogs/CollectionConfig.cpp configdialog/dialogs/ExcludedLabelsDialog.cpp configdialog/dialogs/GeneralConfig.cpp configdialog/dialogs/MetadataConfig.cpp configdialog/dialogs/NotificationsConfig.cpp configdialog/dialogs/PlaybackConfig.cpp configdialog/dialogs/PluginsConfig.cpp configdialog/dialogs/ScriptsConfig.cpp configdialog/dialogs/ScriptSelector.cpp configdialog/dialogs/DatabaseConfig.cpp ) ki18n_wrap_ui(libconfigdialog_SRCS configdialog/dialogs/CollectionConfig.ui configdialog/dialogs/GeneralConfig.ui configdialog/dialogs/MetadataConfig.ui configdialog/dialogs/ExcludedLabelsDialog.ui configdialog/dialogs/NotificationsConfig.ui configdialog/dialogs/PlaybackConfig.ui configdialog/dialogs/DatabaseConfig.ui configdialog/dialogs/ScriptsConfig.ui ) set(libbrowserframework_SRCS browsers/BrowserBreadcrumbItem.cpp browsers/BrowserBreadcrumbWidget.cpp browsers/BrowserCategory.cpp browsers/BrowserCategoryList.cpp browsers/BrowserCategoryListModel.cpp browsers/BrowserCategoryListSortFilterProxyModel.cpp browsers/BrowserDock.cpp browsers/BrowserMessageArea.cpp browsers/CollectionSortFilterProxyModel.cpp browsers/CollectionTreeItem.cpp browsers/CollectionTreeItemModel.cpp browsers/CollectionTreeItemModelBase.cpp browsers/CollectionTreeView.cpp browsers/InfoProxy.cpp browsers/SingleCollectionTreeItemModel.cpp ) ##################################################################### # COLLECTIONBROWSER ##################################################################### set(libcollectionbrowser_SRCS browsers/collectionbrowser/CollectionBrowserTreeView.cpp browsers/collectionbrowser/CollectionWidget.cpp ) ##################################################################### # SYNCHRONIZATION ##################################################################### set(libsynchronization_SRCS synchronization/MasterSlaveSynchronizationJob.cpp synchronization/OneWaySynchronizationJob.cpp synchronization/SynchronizationBaseJob.cpp synchronization/UnionJob.cpp ) ##################################################################### # STATUSBAR ##################################################################### set(libstatusbar_SRCS statusbar/ProgressBar.cpp statusbar/KJobProgressBar.cpp statusbar/NetworkProgressBar.cpp statusbar/CompoundProgressBar.cpp statusbar/PopupWidget.cpp statusbar/LongMessageWidget.cpp ) ##################################################################### # META ##################################################################### set(libmetaimpl_SRCS core-impl/playlists/providers/user/UserPlaylistProvider.cpp core-impl/playlists/types/file/asx/ASXPlaylist.cpp core-impl/playlists/types/file/m3u/M3UPlaylist.cpp core-impl/playlists/types/file/pls/PLSPlaylist.cpp core-impl/playlists/types/file/PlaylistFileLoaderJob.cpp core-impl/playlists/types/file/PlaylistFileSupport.cpp core-impl/playlists/types/file/xspf/XSPFPlaylist.cpp core-impl/capabilities/AlbumActionsCapability.cpp core-impl/capabilities/timecode/TimecodeBoundedPlaybackCapability.cpp core-impl/capabilities/timecode/TimecodeLoadCapability.cpp core-impl/capabilities/timecode/TimecodeWriteCapability.cpp core-impl/capabilities/multisource/MultiSourceCapabilityImpl.cpp core-impl/meta/file/File.cpp core-impl/meta/file/FileTrackProvider.cpp core-impl/meta/multi/MultiTrack.cpp core-impl/meta/cue/CueFileSupport.cpp core-impl/meta/proxy/MetaProxy.cpp core-impl/meta/proxy/MetaProxyWorker.cpp core-impl/meta/stream/Stream.cpp core-impl/playlists/types/file/PlaylistFile.cpp core-impl/support/PersistentStatisticsStore.cpp core-impl/support/TagStatisticsStore.cpp core-impl/support/UrlStatisticsStore.cpp ) ##################################################################### # COLLECTION ##################################################################### set(collection_SRCS core-impl/collections/support/jobs/WriteTagsJob.cpp core-impl/collections/support/ArtistHelper.cpp core-impl/collections/support/CollectionManager.cpp core-impl/collections/support/CollectionLocationDelegateImpl.cpp core-impl/collections/support/MemoryCustomValue.cpp core-impl/collections/support/MemoryFilter.cpp core-impl/collections/support/MemoryMatcher.cpp core-impl/collections/support/MemoryMeta.cpp core-impl/collections/support/MemoryQueryMaker.cpp core-impl/collections/support/MemoryQueryMakerInternal.cpp core-impl/collections/support/MemoryQueryMakerHelper.cpp core-impl/collections/support/TrashCollectionLocation.cpp core-impl/collections/support/XmlQueryReader.cpp core-impl/collections/support/FileCollectionLocation.cpp core-impl/collections/support/Expression.cpp core-impl/collections/support/TextualQueryFilter.cpp ) ##################################################################### # STORAGE ##################################################################### set(storage_SRCS core-impl/storage/StorageManager.cpp ) ##################################################################### # SCANNER ##################################################################### set( scanner_SRCS scanner/GenericScanManager.cpp scanner/GenericScannerJob.cpp scanner/AbstractDirectoryWatcher.cpp scanner/AbstractScanResultProcessor.cpp ) ##################################################################### # CONTEXT ##################################################################### set( libcontextview_SRCS context/AmarokContextPackageStructure.cpp context/AppletLoader.cpp context/AppletModel.cpp context/ContextDock.cpp context/ContextView.cpp - context/LyricsManager.cpp +) + +##################################################################### +# LYRICS +##################################################################### +set( liblyrics_SRCS + lyrics/LyricsManager.cpp ) ##################################################################### # PODCASTS ##################################################################### set(libpodcasts_SRCS core-impl/podcasts/sql/SqlPodcastMeta.cpp core-impl/podcasts/sql/SqlPodcastProvider.cpp core-impl/podcasts/sql/PodcastSettingsDialog.cpp core-impl/podcasts/sql/PodcastFilenameLayoutConfigDialog.cpp ) ##################################################################### # PLAYLISTBROWSER ##################################################################### set(libplaylistbrowser_SRCS browsers/playlistbrowser/APGCategory.cpp browsers/playlistbrowser/DynamicCategory.cpp browsers/playlistbrowser/DynamicBiasDelegate.cpp browsers/playlistbrowser/DynamicBiasDialog.cpp browsers/playlistbrowser/DynamicView.cpp browsers/playlistbrowser/PlaylistBrowserFilterProxy.cpp browsers/playlistbrowser/PlaylistBrowserModel.cpp browsers/playlistbrowser/PlaylistBrowserCategory.cpp browsers/playlistbrowser/QtGroupingProxy.cpp browsers/playlistbrowser/PlaylistBrowser.cpp browsers/playlistbrowser/PlaylistBrowserView.cpp browsers/playlistbrowser/UserPlaylistCategory.cpp browsers/playlistbrowser/PlaylistsInFoldersProxy.cpp browsers/playlistbrowser/PlaylistsByProviderProxy.cpp browsers/playlistbrowser/PodcastModel.cpp browsers/playlistbrowser/PodcastCategory.cpp browsers/playlistbrowser/UserPlaylistModel.cpp ) ##################################################################### # PLAYLISTMANAGER ##################################################################### set(libplaylistmanager_SRCS playlistmanager/PlaylistManager.cpp playlistmanager/file/PlaylistFileProvider.cpp playlistmanager/file/KConfigSyncRelStore.cpp playlistmanager/sql/SqlUserPlaylistProvider.cpp playlistmanager/sql/SqlPlaylist.cpp playlistmanager/sql/SqlPlaylistGroup.cpp playlistmanager/SyncedPlaylist.cpp playlistmanager/SyncedPodcast.cpp playlistmanager/SyncRelationStorage.cpp ) ##################################################################### # PLAYLIST ##################################################################### set(libplaylist_SRCS playlist/PlaylistActions.cpp playlist/PlaylistBreadcrumbItem.cpp playlist/PlaylistBreadcrumbItemSortButton.cpp playlist/PlaylistBreadcrumbLevel.cpp playlist/PlaylistDefines.cpp playlist/PlaylistController.cpp playlist/PlaylistInfoWidget.cpp playlist/PlaylistItem.cpp playlist/PlaylistModel.cpp playlist/PlaylistModelStack.cpp playlist/PlaylistRestorer.cpp playlist/PlaylistQueueEditor.cpp playlist/PlaylistSortWidget.cpp playlist/PlaylistViewUrlGenerator.cpp playlist/PlaylistViewUrlRunner.cpp playlist/PlaylistDock.cpp playlist/PlaylistToolBar.cpp playlist/ProgressiveSearchWidget.cpp playlist/UndoCommands.cpp playlist/layouts/LayoutEditDialog.cpp playlist/layouts/LayoutEditWidget.cpp playlist/layouts/LayoutConfigAction.cpp playlist/layouts/LayoutItemConfig.cpp playlist/layouts/LayoutManager.cpp playlist/layouts/PlaylistLayoutEditDialog.cpp playlist/navigators/AlbumNavigator.cpp playlist/navigators/DynamicTrackNavigator.cpp playlist/navigators/FavoredRandomTrackNavigator.cpp playlist/navigators/NavigatorConfigAction.cpp playlist/navigators/NonlinearTrackNavigator.cpp playlist/navigators/RandomAlbumNavigator.cpp playlist/navigators/RandomTrackNavigator.cpp playlist/navigators/RepeatAlbumNavigator.cpp playlist/navigators/RepeatTrackNavigator.cpp playlist/navigators/StandardTrackNavigator.cpp playlist/navigators/TrackNavigator.cpp playlist/view/PlaylistViewCommon.cpp playlist/view/listview/InlineEditorWidget.cpp playlist/view/listview/PrettyItemDelegate.cpp playlist/view/listview/PrettyListView.cpp playlist/view/listview/SourceSelectionPopup.cpp playlist/proxymodels/GroupingProxy.cpp playlist/proxymodels/ProxyBase.cpp playlist/proxymodels/SortAlgorithms.cpp playlist/proxymodels/SortFilterProxy.cpp playlist/proxymodels/SortScheme.cpp playlist/proxymodels/SearchProxy.cpp ) ki18n_wrap_ui(libplaylist_SRCS playlist/PlaylistQueueEditor.ui ) ##################################################################### # DYNAMIC ##################################################################### set(libdynamic_SRCS dynamic/TrackSet.cpp dynamic/BiasFactory.cpp dynamic/BiasedPlaylist.cpp dynamic/BiasSolver.cpp dynamic/DynamicPlaylist.cpp dynamic/DynamicModel.cpp # biases dynamic/Bias.cpp dynamic/biases/AlbumPlayBias.cpp dynamic/biases/EchoNestBias.cpp dynamic/biases/IfElseBias.cpp dynamic/biases/PartBias.cpp dynamic/biases/QuizPlayBias.cpp dynamic/biases/TagMatchBias.cpp dynamic/biases/SearchQueryBias.cpp ) ##################################################################### # DBUS ##################################################################### set(dbus_SRCS dbus/mpris1/RootHandler.cpp dbus/mpris1/PlayerHandler.cpp dbus/mpris1/TrackListHandler.cpp dbus/mpris2/DBusAbstractAdaptor.cpp dbus/mpris2/Mpris2.cpp dbus/mpris2/MediaPlayer2.cpp dbus/mpris2/MediaPlayer2Player.cpp dbus/mpris2/MediaPlayer2AmarokExtensions.cpp dbus/mpris2/DBusAmarokApp.cpp dbus/CollectionDBusHandler.cpp dbus/DBusQueryHelper.cpp ) ##################################################################### # SCRIPTING INTERFACE ##################################################################### set(scriptengine_SRCS scripting/scriptengine/AmarokBookmarkScript.cpp scripting/scriptengine/AmarokCollectionScript.cpp scripting/scriptengine/AmarokCollectionViewScript.cpp scripting/scriptengine/AmarokEngineScript.cpp scripting/scriptengine/AmarokEqualizerScript.cpp scripting/scriptengine/AmarokInfoScript.cpp scripting/scriptengine/AmarokKNotifyScript.cpp - scripting/scriptengine/AmarokLyricsScript.cpp scripting/scriptengine/AmarokNetworkScript.cpp scripting/scriptengine/AmarokOSDScript.cpp scripting/scriptengine/AmarokPlaylistManagerScript.cpp scripting/scriptengine/AmarokPlaylistScript.cpp scripting/scriptengine/AmarokScript.cpp scripting/scriptengine/AmarokScriptConfig.cpp scripting/scriptengine/AmarokScriptableServiceScript.cpp scripting/scriptengine/AmarokServicePluginManagerScript.cpp scripting/scriptengine/AmarokStatusbarScript.cpp scripting/scriptengine/AmarokStreamItemScript.cpp scripting/scriptengine/AmarokWindowScript.cpp scripting/scriptengine/AmarokScriptXml.cpp scripting/scriptengine/ScriptImporter.cpp scripting/scriptengine/ScriptingDefines.cpp scripting/scriptengine/exporters/CollectionTypeExporter.cpp scripting/scriptengine/exporters/MetaTypeExporter.cpp scripting/scriptengine/exporters/PlaylistExporter.cpp scripting/scriptengine/exporters/PlaylistProviderExporter.cpp scripting/scriptengine/exporters/QueryMakerExporter.cpp scripting/scriptengine/exporters/ScriptableBiasExporter.cpp ) set(scriptconsole_SRCS scripting/scriptconsole/CompletionModel.cpp scripting/scriptconsole/ScriptConsole.cpp scripting/scriptconsole/ScriptEditorDocument.cpp scripting/scriptconsole/ScriptConsoleItem.cpp ) if (PYTHONINTERP_FOUND) execute_process(COMMAND "${PYTHON_EXECUTABLE}" ${CMAKE_SOURCE_DIR}/src/scripting/scriptengine/PHAACG2.py ${CMAKE_SOURCE_DIR}/src/scripting/scriptengine ${CMAKE_BINARY_DIR}/scriptconsole) install(FILES ${CMAKE_BINARY_DIR}/scriptconsole/AutoComplete.txt DESTINATION ${KDE_INSTALL_DATADIR}/amarok/scriptconsole) endif() ##################################################################### # PLAYLIST GENERATOR ##################################################################### set(apg_SRCS playlistgenerator/Constraint.cpp playlistgenerator/ConstraintGroup.cpp playlistgenerator/ConstraintFactory.cpp playlistgenerator/ConstraintNode.cpp playlistgenerator/ConstraintSolver.cpp playlistgenerator/Preset.cpp playlistgenerator/PresetEditDialog.cpp playlistgenerator/PresetModel.cpp playlistgenerator/TreeController.cpp playlistgenerator/TreeModel.cpp playlistgenerator/constraints/Checkpoint.cpp playlistgenerator/constraints/Matching.cpp playlistgenerator/constraints/PlaylistDuration.cpp playlistgenerator/constraints/PlaylistFileSize.cpp playlistgenerator/constraints/PlaylistLength.cpp playlistgenerator/constraints/PreventDuplicates.cpp playlistgenerator/constraints/TagMatch.cpp playlistgenerator/constraints/TagMatchSupport.cpp playlistgenerator/constraints/TrackSpreader.cpp ) ki18n_wrap_ui(apg_SRCS playlistgenerator/ConstraintGroupEditWidget.ui playlistgenerator/PresetEditDialog.ui playlistgenerator/constraints/CheckpointEditWidget.ui playlistgenerator/constraints/PlaylistDurationEditWidget.ui playlistgenerator/constraints/PlaylistFileSizeEditWidget.ui playlistgenerator/constraints/PlaylistLengthEditWidget.ui playlistgenerator/constraints/PreventDuplicatesEditWidget.ui playlistgenerator/constraints/TagMatchEditWidget.ui ) ##################################################################### # NETWORK ACCESS ##################################################################### set(network_access_SRCS network/NetworkAccessManagerProxy.cpp ) if( CMAKE_BUILD_TYPE_TOLOWER MATCHES debug ) set(network_access_SRCS ${network_access_SRCS} network/NetworkAccessViewer.cpp ) ki18n_wrap_ui(network_access_SRCS network/NetworkRequests.ui ) endif() ##################################################################### # STATISTICS SYNCHRONIZATION ##################################################################### set( statsyncing_SRCS statsyncing/Config.cpp statsyncing/Controller.cpp statsyncing/Options.cpp statsyncing/Process.cpp statsyncing/Provider.cpp statsyncing/ProviderFactory.cpp statsyncing/ScrobblingService.cpp statsyncing/SimpleTrack.cpp statsyncing/SimpleWritableTrack.cpp statsyncing/Track.cpp statsyncing/TrackTuple.cpp statsyncing/collection/CollectionProvider.cpp statsyncing/collection/CollectionTrack.cpp statsyncing/jobs/MatchTracksJob.cpp statsyncing/jobs/SynchronizeTracksJob.cpp statsyncing/models/CommonModel.cpp statsyncing/models/MatchedTracksModel.cpp statsyncing/models/ProvidersModel.cpp statsyncing/models/SingleTracksModel.cpp statsyncing/ui/ChooseProvidersPage.cpp statsyncing/ui/CreateProviderDialog.cpp statsyncing/ui/ConfigureProviderDialog.cpp statsyncing/ui/MatchedTracksPage.cpp statsyncing/ui/TrackDelegate.cpp ) ki18n_wrap_ui( statsyncing_SRCS statsyncing/ui/ChooseProvidersPage.ui statsyncing/ui/MatchedTracksPage.ui ) ##################################################################### # STATISTICS IMPORTERS ##################################################################### set( importers_SRCS importers/ImporterManager.cpp importers/ImporterProvider.cpp importers/ImporterSqlConnection.cpp importers/SimpleImporterConfigWidget.cpp ) ##################################################################### # LIBAMAROK ##################################################################### set(amaroklib_LIB_SRCS ${libscriptableservice_SRCS} ${libbrowserframework_SRCS} ${libcontextview_SRCS} ${libcollectionbrowser_SRCS} ${libconfigdialog_SRCS} + ${liblyrics_SRCS} ${libplaylist_SRCS} ${aggregatecollection_SRCS} ${libpodcasts_SRCS} ${libmediadeviceframework_SRCS} ${libserviceframework_SRCS} ${libservicebrowser_SRCS} ${libdynamic_SRCS} ${libmetaimpl_SRCS} ${apg_SRCS} ${collection_SRCS} ${storage_SRCS} ${scanner_SRCS} ${mac_SRCS} ${network_access_SRCS} ${libplaylistbrowser_SRCS} ${libplaylistmanager_SRCS} ${dbus_SRCS} ${scriptengine_SRCS} ${scriptconsole_SRCS} ${libstatusbar_SRCS} ${libamarokurl_SRCS} ${libsynchronization_SRCS} ${statsyncing_SRCS} ${importers_SRCS} aboutdialog/AnimatedBarWidget.cpp aboutdialog/AnimatedWidget.cpp aboutdialog/ExtendedAboutDialog.cpp aboutdialog/FramedLabel.cpp aboutdialog/OcsData.cpp aboutdialog/OcsPersonItem.cpp aboutdialog/OcsPersonListWidget.cpp ActionClasses.cpp AmarokMimeData.cpp AmarokProcess.cpp App.cpp CaseConverter.cpp EngineController.cpp KNotificationBackend.cpp MainWindow.cpp MediaDeviceCache.cpp MediaDeviceMonitor.cpp PluginManager.cpp QStringx.cpp scripting/scriptmanager/ScriptManager.cpp scripting/scriptmanager/ScriptItem.cpp scripting/scriptmanager/ScriptUpdater.cpp SvgHandler.cpp SvgTinter.cpp TrayIcon.cpp core-impl/logger/DebugLogger.cpp core-impl/meta/timecode/TimecodeObserver.cpp core-impl/meta/timecode/TimecodeMeta.cpp core-impl/meta/timecode/TimecodeTrackProvider.cpp core-impl/support/TrackLoader.cpp covermanager/CoverCache.cpp covermanager/CoverFetcher.cpp covermanager/CoverFetchingActions.cpp covermanager/CoverFetchQueue.cpp covermanager/CoverFetchUnit.cpp covermanager/CoverFoundDialog.cpp covermanager/CoverManager.cpp covermanager/CoverViewDialog.cpp databaseimporter/SqlBatchImporter.cpp databaseimporter/SqlBatchImporterConfig.cpp dialogs/CollectionSetup.cpp dialogs/DatabaseImporterDialog.cpp dialogs/DiagnosticDialog.cpp dialogs/EditFilterDialog.cpp dialogs/EqualizerDialog.cpp dialogs/MusicBrainzTagger.cpp dialogs/OrganizeCollectionDialog.cpp dialogs/TrackOrganizer.cpp dialogs/TagDialog.cpp dialogs/TagGuesser.cpp dialogs/TagGuesserDialog.cpp dialogs/LabelListModel.cpp equalizer/EqualizerPresets.cpp browsers/filebrowser/DirPlaylistTrackFilterProxyModel.cpp browsers/filebrowser/FileBrowser.cpp browsers/filebrowser/FileView.cpp musicbrainz/MusicBrainzFinder.cpp musicbrainz/MusicBrainzTagsItem.cpp musicbrainz/MusicBrainzTagsModel.cpp musicbrainz/MusicBrainzTagsModelDelegate.cpp musicbrainz/MusicBrainzTagsView.cpp musicbrainz/MusicBrainzXmlParser.cpp OpmlOutline.cpp OpmlParser.cpp OpmlWriter.cpp PaletteHandler.cpp PopupDropperFactory.cpp playback/DelayedDoers.cpp playback/EqualizerController.cpp playback/Fadeouter.cpp playback/PowerManager.cpp statemanagement/ApplicationController.cpp statemanagement/DefaultApplicationController.cpp toolbar/CurrentTrackToolbar.cpp toolbar/SlimToolbar.cpp toolbar/VolumePopupButton.cpp toolbar/MainToolbar.cpp widgets/AlbumBreadcrumbWidget.cpp widgets/AmarokDockWidget.cpp widgets/AnimatedLabelStack.cpp widgets/BoxWidget.cpp widgets/BreadcrumbItemButton.cpp widgets/ClearSpinBox.cpp widgets/CoverLabel.cpp widgets/HintLineEdit.cpp widgets/kdatecombo.cpp widgets/TokenDropTarget.cpp widgets/EditDeleteComboBoxView.cpp widgets/EditDeleteDelegate.cpp widgets/ElidingButton.cpp widgets/FilenameLayoutWidget.cpp widgets/FlowLayout.cpp widgets/HorizontalDivider.cpp widgets/IconButton.cpp widgets/ComboBox.cpp widgets/LineEdit.cpp widgets/Osd.cpp widgets/TimeLabel.cpp widgets/PixmapViewer.cpp widgets/PlayPauseButton.cpp widgets/PrettyTreeView.cpp widgets/PrettyTreeDelegate.cpp widgets/ProgressWidget.cpp widgets/SearchWidget.cpp widgets/SliderWidget.cpp widgets/StarManager.cpp widgets/TokenPool.cpp widgets/Token.cpp widgets/TokenWithLayout.cpp widgets/VolumeDial.cpp widgets/TrackActionButton.cpp widgets/BookmarkTriangle.cpp widgets/BookmarkPopup.cpp widgets/TrackSelectWidget.cpp widgets/MetaQueryWidget.cpp GlobalCollectionActions.cpp GlobalCurrentTrackActions.cpp moodbar/MoodbarManager.cpp ) if( LIBLASTFM_FOUND ) set(amaroklib_LIB_SRCS ${amaroklib_LIB_SRCS} LastfmReadLabelCapability.cpp ) include_directories( ${LIBLASTFM_INCLUDE_DIR}/.. ${LIBLASTFM_INCLUDE_DIR}) set( EXTRA_LIBS ${LIBLASTFM_LIBRARY} ) endif() if( LIBOFA_FOUND AND AVCODEC_FOUND AND AVFORMAT_FOUND AND AVUTIL_FOUND ) add_definitions( ${AVCODEC_DEFINITIONS} ${AVFORMAT_DEFINITIONS} ${AVUTIL_DEFINITIONS} ) include_directories( ${AVCODEC_INCLUDE_DIRS} ${AVFORMAT_INCLUDE_DIRS} ${AVUTIL_INCLUDE_DIRS} ) set( EXTRA_LIBS ${EXTRA_LIBS} ${LIBOFA_LIBRARY} ${AVFORMAT_LIBRARIES} ${AVCODEC_LIBRARIES} ${AVUTIL_LIBRARIES} ) set( amaroklib_LIB_SRCS ${amaroklib_LIB_SRCS} musicbrainz/MusicDNSAudioDecoder.cpp musicbrainz/MusicDNSFinder.cpp musicbrainz/MusicDNSXmlParser.cpp ) endif() qt5_add_dbus_adaptor( AMAROK_MPRIS1_ADAPTOR_SRCS dbus/mpris1/org.freedesktop.MediaPlayer.root.xml dbus/mpris1/RootHandler.h Mpris1::RootHandler Mpris1RootAdaptor Mpris1RootAdaptor ) qt5_add_dbus_adaptor( AMAROK_MPRIS1_ADAPTOR_SRCS dbus/mpris1/org.freedesktop.MediaPlayer.player.xml dbus/mpris1/PlayerHandler.h Mpris1::PlayerHandler Mpris1PlayerAdaptor Mpris1PlayerAdaptor ) qt5_add_dbus_adaptor( AMAROK_MPRIS1_ADAPTOR_SRCS dbus/mpris1/org.freedesktop.MediaPlayer.tracklist.xml dbus/mpris1/TrackListHandler.h Mpris1::TrackListHandler Mpris1TrackListAdaptor Mpris1TrackListAdaptor ) qt5_add_dbus_adaptor( AMAROK_MPRIS1_ADAPTOR_SRCS dbus/org.kde.amarok.App.xml dbus/mpris1/RootHandler.h Mpris1::RootHandler Mpris1AmarokAppAdaptor Mpris1AmarokAppAdaptor ) qt5_add_dbus_adaptor( AMAROK_MPRIS1_ADAPTOR_SRCS dbus/mpris1/org.kde.amarok.Mpris1Extensions.Player.xml dbus/mpris1/PlayerHandler.h Mpris1::PlayerHandler Mpris1AmarokPlayerAdaptor Mpris1AmarokPlayerAdaptor ) qt5_add_dbus_adaptor( AMAROK_MPRIS1_ADAPTOR_SRCS dbus/org.kde.amarok.Collection.xml dbus/CollectionDBusHandler.h CollectionDBusHandler CollectionAdaptor CollectionAdaptor ) # suppress deprecated methods warnings if(NOT WIN32) set_source_files_properties(${AMAROK_MPRIS1_ADAPTOR_SRCS} PROPERTIES COMPILE_FLAGS -Wno-deprecated-declarations) endif() set( amaroklib_DEPENDS "amarokpud" ) set( amaroklib_DEPENDS "amarokcore" ) set( amaroklib_DEPENDS "amarok-transcoding" ) # depends on generated ui_*.h file kconfig_add_kcfg_files(amaroklib_LIB_SRCS amarokconfig.kcfgc) add_custom_target(amarokconfig_h DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/amarokconfig.h) ki18n_wrap_ui(amaroklib_LIB_SRCS aboutdialog/OcsPersonItem.ui dialogs/EditFilterDialog.ui dialogs/EqualizerDialog.ui dialogs/MusicBrainzTagger.ui dialogs/TagDialogBase.ui dialogs/TagGuessOptions.ui dialogs/OrganizeCollectionOptions.ui dialogs/OrganizeCollectionDialogBase.ui playlist/layouts/PlaylistLayoutEditDialog.ui core-impl/podcasts/sql/PodcastSettingsBase.ui core-impl/podcasts/sql/SqlPodcastProviderSettingsWidget.ui core-impl/podcasts/sql/PodcastFilenameLayoutConfigWidget.ui browsers/playlistbrowser/PodcastCategoryBase.ui ) add_library(amaroklib SHARED ${amaroklib_LIB_SRCS} ${AMAROK_MPRIS1_ADAPTOR_SRCS}) target_link_libraries(amaroklib Phonon::phonon4qt5 KF5::Archive KF5::CoreAddons KF5::Declarative KF5::GlobalAccel KF5::GuiAddons KF5::I18n KF5::IconThemes KF5::KCMUtils KF5::KIOCore KF5::KIOFileWidgets KF5::KIOWidgets KF5::KIONTLM KF5::NewStuff KF5::Notifications KF5::NotifyConfig KF5::Package KF5::TextEditor KF5::ThreadWeaver KF5::WindowSystem ${QT_QTSCRIPTTOOLS_LIBRARY} Qt5::Gui Qt5::Quick Qt5::QuickWidgets Qt5::Script Qt5::ScriptTools Qt5::Sql Qt5::Svg ${CMAKE_DL_LIBS} Threads::Threads ${EXTRA_LIBS} amarokpud amarokcore amarokocsclient amarok-transcoding amarokshared ) if( Qt5WebEngine_FOUND ) target_link_libraries( amaroklib Qt5::WebEngine ) add_definitions( -DWITH_QT_WEBENGINE ) endif() include_directories(${TAGLIB_INCLUDES}) add_definitions(${TAGLIB_CFLAGS}) target_link_libraries(amaroklib ${TAGLIB_LIBRARIES}) if( TAGLIB-EXTRAS_FOUND ) include_directories(${TAGLIB-EXTRAS_INCLUDES}) add_definitions(${TAGLIB-EXTRAS_CFLAGS}) target_link_libraries(amaroklib ${TAGLIB-EXTRAS_LIBRARIES}) endif() if(WIN32) target_link_libraries(amaroklib Qt5::WebKitWidgets) endif() if(APPLE) target_link_libraries(amaroklib "/System/Library/Frameworks/Foundation.framework") set_target_properties(amaroklib PROPERTIES LINK_FLAGS "-undefined dynamic_lookup") endif() set_target_properties(amaroklib PROPERTIES VERSION 1.0.0 SOVERSION 1 ) install(TARGETS amaroklib ${INSTALL_TARGETS_DEFAULT_ARGS} ) ##################################################################### # AMAROK ##################################################################### set( amarok_SRCS main.cpp ) file(GLOB ICONS_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/../images/*-apps-amarok.png) ecm_add_app_icon(amarok_SRCS ICONS ${ICONS_SRCS}) add_executable(amarok ${amarok_SRCS}) if(APPLE) set_target_properties(amarok PROPERTIES LINK_FLAGS "-undefined dynamic_lookup") set(MACOSX_BUNDLE_BUNDLE_NAME "Amarok 2") set(MACOSX_BUNDLE_BUNDLE_VERSION "2.8.0-git") set(MACOSX_BUNDLE_COPYRIGHT "Amarok Team") set_target_properties(amarok PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${MAC_FILES_DIR}/Info.plist.template) set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/amarok.icns PROPERTIES MACOSX_PACKAGE_LOCATION Resources) endif() target_link_libraries(amarok KF5::CoreAddons KF5::DBusAddons KF5::I18n amarokcore amaroklib ${X11_LIBRARIES} ) install(TARGETS amarok ${INSTALL_TARGETS_DEFAULT_ARGS}) ########### install files ############### install(PROGRAMS org.kde.amarok.desktop DESTINATION ${KDE_INSTALL_APPDIR} ) install(PROGRAMS org.kde.amarok_containers.desktop DESTINATION ${KDE_INSTALL_APPDIR} ) install(FILES org.kde.amarok.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) install(FILES amarok-plugin.desktop DESTINATION ${KDE_INSTALL_KSERVICETYPES5DIR}) install(FILES amarok-contextapplet.desktop DESTINATION ${KDE_INSTALL_KSERVICETYPES5DIR}) install(FILES amarok_codecinstall.desktop DESTINATION ${KDE_INSTALL_KSERVICETYPES5DIR}) install(FILES amarok_append.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/ServiceMenus) install(FILES amarok-play-audiocd.desktop DESTINATION ${KDE_INSTALL_DATADIR}/solid/actions) install(FILES amarok.knsrc DESTINATION ${KDE_INSTALL_CONFDIR}) # protocol handlers install(FILES amarokurls/amarok.protocol DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) install(FILES amarokitpc.protocol DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) #install(FILES amarokpcast.protocol DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) install(FILES amarokconfig.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR} ) install(FILES dbus/mpris1/org.freedesktop.MediaPlayer.root.xml dbus/mpris1/org.freedesktop.MediaPlayer.player.xml dbus/mpris1/org.freedesktop.MediaPlayer.tracklist.xml dbus/org.kde.amarok.App.xml dbus/org.kde.amarok.Collection.xml dbus/mpris1/org.kde.amarok.Mpris1Extensions.Player.xml dbus/mpris2/org.kde.amarok.Mpris2Extensions.Player.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR}) install(FILES services/InfoParserLoading.html browsers/hover_info_template.html DESTINATION ${KDE_INSTALL_DATADIR}/amarok/data) ecm_install_icons(ICONS DESTINATION ${KDE_INSTALL_ICONDIR} THEME hicolor ) diff --git a/src/OpmlParser.h b/src/OpmlParser.h index 231b24c3bb..a1086743bd 100644 --- a/src/OpmlParser.h +++ b/src/OpmlParser.h @@ -1,281 +1,281 @@ /**************************************************************************************** * Copyright (c) 2008 Nikolaj Hald Nielsen * * Copyright (c) 2009 Bart Cerneels * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #ifndef OPMLPARSER_H #define OPMLPARSER_H #include "amarok_export.h" #include "OpmlOutline.h" #include #include #include #include #include #include #include namespace KIO { class Job; class TransferJob; } /** * Parser for OPML files. */ class AMAROK_EXPORT OpmlParser : public QObject, public ThreadWeaver::Job, public QXmlStreamReader { Q_OBJECT public: static const QString OPML_MIME; /** * Constructor * @param fileName The file to parse * @return Pointer to new object */ explicit OpmlParser( const QUrl &url ); /** * Destructor * @return none */ ~OpmlParser(); /** * The function that starts the actual work. Inherited from ThreadWeaver::Job * Note the work is performed in a separate thread * @return Returns true on success and false on failure */ - void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) Q_DECL_OVERRIDE; + void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) override; bool read( const QUrl &url ); bool read( QIODevice *device ); /** @return the URL of the OPML being parsed. */ QUrl url() const { return m_url; } QMap headerData() { return m_headerData; } /** * Get the result of the parsing as a list of OpmlOutlines. * This list contains only root outlines that can be found in the of the OPML. * The rest are children of these root items. * * The user is responsible for deleting the results. */ QList results() const { return m_outlines; } protected: - void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; + void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; Q_SIGNALS: /** * Emitted when has been completely parsed. */ void headerDone(); /** * Signal emmited when parsing is complete. * The data is complete now and accessible via results(). * Children of all the outlines are available via OpmlOutline::children(). */ void doneParsing(); /** * Emitted when a new outline item is available. * Emitted after the attributes have been read but before any of the children are available. * Each child will be reported in a separate signal. */ void outlineParsed( OpmlOutline *outline ); /** 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); public Q_SLOTS: virtual void slotAbort(); private Q_SLOTS: void slotAddData( KIO::Job *, const QByteArray &data ); void downloadResult( KJob * ); private: enum ElementType { Unknown = 0, Any, Document, CharacterData, Opml, Html, Head, Title, DateCreated, DateModified, OwnerName, OwnerEmail, OwnerId, Docs, ExpansionState, VertScrollState, WindowTop, WindowLeft, WindowBottom, WindowRight, Body, Outline }; class Action; typedef void (OpmlParser::*ActionCallback)(); typedef QHash ActionMap; class Action { public: explicit Action( ActionMap &actionMap ) : m_actionMap( actionMap ) , m_begin( 0 ) , m_end( 0 ) , m_characters( 0 ) {} Action(ActionMap &actionMap, ActionCallback begin) : m_actionMap( actionMap ) , m_begin( begin ) , m_end( 0 ) , m_characters( 0 ) {} Action(ActionMap &actionMap, ActionCallback begin, ActionCallback end) : m_actionMap( actionMap ) , m_begin( begin ) , m_end( end ) , m_characters( 0 ) {} Action(ActionMap &actionMap, ActionCallback begin, ActionCallback end, ActionCallback characters) : m_actionMap( actionMap ) , m_begin( begin ) , m_end( end ) , m_characters( characters ) {} void begin( OpmlParser *opmlParser ) const; void end( OpmlParser *opmlParser ) const; void characters( OpmlParser *opmlParser ) const; const ActionMap &actionMap() const { return m_actionMap; } private: ActionMap &m_actionMap; ActionCallback m_begin; ActionCallback m_end; ActionCallback m_characters; }; ElementType elementType() const; bool read(); bool continueRead(); // callback methods for parsing void beginOpml(); void beginText(); void beginOutline(); void beginNoElement(); void endDocument(); void endHead(); void endTitle(); void endOutline(); void readCharacters(); void readNoCharacters(); void stopWithError( const QString &message ); class StaticData { public: StaticData(); // This here basically builds an automata. // This way feed parsing can be paused after any token, // thus enabling parallel download and parsing of multiple // feeds without the need for threads. QHash knownElements; //Actions Action startAction; Action docAction; Action skipAction; Action noContentAction; Action opmlAction; Action headAction; Action titleAction; // Action dateCreatedAction; // Action dateModifiedAction; // Action ownerNameAction; // Action ownerEmailAction; // Action ownerIdAction; // Action docsAction; // Action expansionStateAction; Action bodyAction; Action outlineAction; ActionMap rootMap; ActionMap skipMap; ActionMap noContentMap; ActionMap xmlMap; ActionMap docMap; ActionMap opmlMap; ActionMap headMap; ActionMap bodyMap; ActionMap outlineMap; ActionMap textMap; }; static const StaticData sd; QStack m_actionStack; QString m_buffer; QMap m_headerData; // the top level outlines of . QList m_outlines; // currently processing outlines so we can do nested outlines. QStack m_outlineStack; QUrl m_url; KIO::TransferJob *m_transferJob; }; #endif diff --git a/src/OpmlWriter.h b/src/OpmlWriter.h index f792ce7649..6502ef2cdc 100644 --- a/src/OpmlWriter.h +++ b/src/OpmlWriter.h @@ -1,80 +1,80 @@ /**************************************************************************************** * Copyright (c) 2010 Bart Cerneels * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #ifndef OPMLWRITER_H #define OPMLWRITER_H #include "OpmlOutline.h" #include #include #include class AMAROK_EXPORT OpmlWriter : public QObject, public ThreadWeaver::Job { Q_OBJECT public: /** OpmlWriter will write the OPML outline objects as XML text. * @arg rootOutlines the of the OPML * @arg headerData these fields are put in the of the OPML * @arg device QIODevice to write to * The children of IncludeNodes will not be written. Remove the type="include" attribute * from the include node to force a save of those child nodes. */ OpmlWriter( const QList rootOutlines, const QMap headerData, QIODevice *device ); void setHeaderData( const QMap data ) { m_headerData = data; } /** * The function that starts the actual work. Inherited from ThreadWeaver::Job * Note the work is performed in a separate thread * @return Returns true on success and false on failure */ - void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) Q_DECL_OVERRIDE; + void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) override; QIODevice *device() { return m_xmlWriter->device(); } Q_SIGNALS: /** * Signal emmited when writing is complete. */ void result( int error ); /** 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); private: void writeOutline( const OpmlOutline *outline ); QList m_rootOutlines; QMap m_headerData; QUrl m_fileUrl; QXmlStreamWriter *m_xmlWriter; protected: - void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; + void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; }; #endif // OPMLWRITER_H diff --git a/src/amarokurls/BookmarkGroup.cpp b/src/amarokurls/BookmarkGroup.cpp index d26e211c56..c1e6aae161 100644 --- a/src/amarokurls/BookmarkGroup.cpp +++ b/src/amarokurls/BookmarkGroup.cpp @@ -1,246 +1,244 @@ /**************************************************************************************** * 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 "BookmarkGroup.h" #include "AmarokUrl.h" #include "core-impl/storage/StorageManager.h" #include "core/support/Debug.h" #include #include BookmarkGroup::BookmarkGroup( const QStringList & dbResultRow, BookmarkGroupPtr parent ) : BookmarkViewItem() , m_parent( parent ) , m_customType() , m_hasFetchedChildGroups( false ) , m_hasFetchedChildPlaylists( false ) { m_dbId = dbResultRow[0].toInt(); m_name = dbResultRow[2]; m_description = dbResultRow[3]; } BookmarkGroup::BookmarkGroup( const QString & name, BookmarkGroupPtr parent ) : BookmarkViewItem() , m_dbId( -1 ) , m_parent( parent ) , m_name( name ) , m_description() , m_customType() , m_hasFetchedChildGroups( false ) , m_hasFetchedChildPlaylists( false ) { } BookmarkGroup::BookmarkGroup( const QString &name, const QString &customType ) : BookmarkViewItem() { DEBUG_BLOCK; m_parent = BookmarkGroupPtr(); m_hasFetchedChildGroups = false; m_hasFetchedChildPlaylists = false; m_customType = customType; debug() << "custom type: " << customType << " named '" << name << "'"; //check if this custom group already exists and if so, just load that data. QString query = "SELECT id, parent_id, name, description FROM bookmark_groups where custom='%1';"; query = query.arg( customType ); QStringList result = StorageManager::instance()->sqlStorage()->query( query ); if ( result.count() == 4 ) { debug() << "already exists, loading..." << result; m_dbId = result[0].toInt(); m_name = result[2]; m_description = result[3]; debug() << "id: " << m_dbId; } else { debug() << "creating new"; //create new and store m_name = name; m_dbId = -1; save(); } } BookmarkGroup::~BookmarkGroup() { //DEBUG_BLOCK //debug() << "deleting " << m_name; clear(); } void BookmarkGroup::save() { DEBUG_BLOCK int parentId = -1; if ( m_parent ) parentId = m_parent->id(); if ( m_dbId != -1 ) { //update existing QString query = "UPDATE bookmark_groups SET parent_id=%1, name='%2', description='%3', custom='%4%' WHERE id=%5;"; query = query.arg( QString::number( parentId ), m_name, m_description, m_customType, QString::number( m_dbId ) ); StorageManager::instance()->sqlStorage()->query( query ); } else { //insert new QString query = "INSERT INTO bookmark_groups ( parent_id, name, description, custom) VALUES ( %1, '%2', '%3', '%4' );"; query = query.arg( QString::number( parentId ), m_name, m_description, m_customType ); m_dbId = StorageManager::instance()->sqlStorage()->insert( query, NULL ); } } BookmarkGroupList BookmarkGroup::childGroups() const { //DEBUG_BLOCK if ( !m_hasFetchedChildGroups ) { QString query = "SELECT id, parent_id, name, description FROM bookmark_groups where parent_id=%1 ORDER BY name;"; query = query.arg( QString::number( m_dbId ) ); QStringList result = StorageManager::instance()->sqlStorage()->query( query ); int resultRows = result.count() / 4; for( int i = 0; i < resultRows; i++ ) { QStringList row = result.mid( i*4, 4 ); BookmarkGroup* mutableThis = const_cast( this ); m_childGroups << BookmarkGroupPtr( new BookmarkGroup( row, BookmarkGroupPtr( mutableThis ) ) ); } m_hasFetchedChildGroups = true; } return m_childGroups; } BookmarkList BookmarkGroup::childBookmarks() const { //DEBUG_BLOCK //debug() << "my name: " << m_name << " my pointer: " << this; if ( !m_hasFetchedChildPlaylists ) { QString query = "SELECT id, parent_id, name, url, description, custom FROM bookmarks where parent_id=%1 ORDER BY name;"; query = query.arg( QString::number( m_dbId ) ); QStringList result = StorageManager::instance()->sqlStorage()->query( query ); //debug() << "Result: " << result; int resultRows = result.count() / 6; for( int i = 0; i < resultRows; i++ ) { QStringList row = result.mid( i*6, 6 ); BookmarkGroup* mutableThis = const_cast( this ); m_childBookmarks << AmarokUrlPtr( new AmarokUrl( row, BookmarkGroupPtr( mutableThis ) ) ); } m_hasFetchedChildPlaylists = true; } return m_childBookmarks; } int BookmarkGroup::id() const { return m_dbId; } QString BookmarkGroup::name() const { return m_name; } QString BookmarkGroup::description() const { return m_description; } int BookmarkGroup::childCount() const { //DEBUG_BLOCK return childGroups().count() + childBookmarks().count(); } void BookmarkGroup::clear() { //DEBUG_BLOCK //m_childBookmarks, m_childGroups are AmarokSharedPointers, so we should be able to just clear the list //and the playlistptrs will delete themselves m_childGroups.clear(); m_childBookmarks.clear(); m_hasFetchedChildGroups = false; m_hasFetchedChildPlaylists = false; } void BookmarkGroup::rename(const QString & name) { m_name = name; save(); } void BookmarkGroup::setDescription( const QString &description ) { m_description = description; save(); } void BookmarkGroup::deleteChild( BookmarkViewItemPtr item ) { - if ( typeid( * item ) == typeid( BookmarkGroup ) ) + if ( auto group = BookmarkGroupPtr::dynamicCast( item ) ) { - BookmarkGroupPtr group = BookmarkGroupPtr::staticCast( item ); m_childGroups.removeAll( group ); } - else if ( typeid( * item ) == typeid( AmarokUrl ) ) + else if ( auto bookmark = AmarokUrlPtr::dynamicCast( item ) ) { - AmarokUrlPtr bookmark = AmarokUrlPtr::staticCast( item ); m_childBookmarks.removeAll( bookmark ); } } void BookmarkGroup::removeFromDb() { DEBUG_BLOCK foreach( BookmarkGroupPtr group, m_childGroups ) group->removeFromDb(); foreach( AmarokUrlPtr bookmark, m_childBookmarks ) bookmark->removeFromDb(); QString query = QString( "DELETE FROM bookmark_groups where id=%1;").arg( QString::number( m_dbId ) ); debug() << "query: " << query; QStringList result = StorageManager::instance()->sqlStorage()->query( query ); } void BookmarkGroup::reparent( BookmarkGroupPtr parent ) { m_parent = parent; save(); } diff --git a/src/amarokurls/BookmarkModel.cpp b/src/amarokurls/BookmarkModel.cpp index fb6193d55b..4d3fd87ff9 100644 --- a/src/amarokurls/BookmarkModel.cpp +++ b/src/amarokurls/BookmarkModel.cpp @@ -1,706 +1,697 @@ /**************************************************************************************** * Copyright (c) 2008 Nikolaj Hald Nielsen * * Copyright (c) 2008 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 . * ****************************************************************************************/ #include "BookmarkModel.h" #include "AmarokMimeData.h" #include "AmarokUrl.h" #include "BookmarkGroup.h" #include "AmarokUrlHandler.h" #include "core/support/Debug.h" #include #include #include #include #include #include static const int BOOKMARK_DB_VERSION = 4; static const QString key("AMAROK_BOOKMARKS"); BookmarkModel * BookmarkModel::s_instance = 0; BookmarkModel * BookmarkModel::instance() { if ( s_instance == 0 ) s_instance = new BookmarkModel(); return s_instance; } BookmarkModel::BookmarkModel() : QAbstractItemModel() { checkTables(); m_root = BookmarkGroupPtr( new BookmarkGroup( "root", BookmarkGroupPtr() ) ); } BookmarkModel::~BookmarkModel() { } QVariant BookmarkModel::data( const QModelIndex & index, int role ) const { if ( !index.isValid() ) return QVariant(); BookmarkViewItemPtr item = m_viewItems.value( index.internalId() ); if( role == 0xf00d ) return QVariant::fromValue( item ); else if( role == Qt::DisplayRole || role == Qt::EditRole ) { switch( index.column() ) { case Name: return item->name(); break; case Command: { AmarokUrl * url = dynamic_cast( item.data() ); if ( url ) return url->prettyCommand(); else return i18n( "Group" ); break; } case Url: { AmarokUrl * url = dynamic_cast( item.data() ); if ( url ) return url->url(); else return QString(); break; } case Description: { return item->description(); break; } default: break; } } else if( role == Qt::DecorationRole ) { if( index.column() == Name ) { - if ( typeid( * item ) == typeid( BookmarkGroup ) ) + if ( BookmarkGroupPtr::dynamicCast( item ) ) return QVariant( QIcon::fromTheme( "folder-bookmark" ) ); - else if ( typeid( * item ) == typeid( AmarokUrl ) ) - { - AmarokUrl * url = static_cast( item.data() ); + else if ( auto url = AmarokUrlPtr::dynamicCast( item ) ) return The::amarokUrlHandler()->iconForCommand( url->command() ); - } } } return QVariant(); } QModelIndex BookmarkModel::createIndex( int row, int column, const BookmarkViewItemPtr &item ) const { quint32 index = qHash( item.data() ); if( !m_viewItems.contains( index ) ) m_viewItems[ index ] = item; QModelIndex ret = QAbstractItemModel::createIndex( row, column, index ); // debug() << "created " << ret << " with " << ret.parent().internalId(); return ret; } QModelIndex BookmarkModel::index( int row, int column, const QModelIndex & parent ) const { //DEBUG_BLOCK //debug() << "row: " << row << ", column: " <childGroups().count(); if ( row < noOfGroups ) { return createIndex( row, column, BookmarkViewItemPtr::staticCast( m_root->childGroups().at( row ) ) ); } else { //debug() << "Root playlist"; return createIndex( row, column, BookmarkViewItemPtr::staticCast( m_root->childBookmarks().at( row - noOfGroups ) ) ); } } else { BookmarkGroupPtr playlistGroup = BookmarkGroupPtr::staticCast( m_viewItems.value( parent.internalId() ) ); int noOfGroups = playlistGroup->childGroups().count(); if ( row < noOfGroups ) { return createIndex( row, column, BookmarkViewItemPtr::staticCast( playlistGroup->childGroups().at(row) ) ); } else { return createIndex( row, column, BookmarkViewItemPtr::staticCast( playlistGroup->childBookmarks().at(row - noOfGroups) ) ); } } } QModelIndex BookmarkModel::parent( const QModelIndex & index ) const { //DEBUG_BLOCK if (!index.isValid()) return QModelIndex(); BookmarkViewItemPtr item = m_viewItems.value( index.internalId() ); BookmarkGroupPtr parent = item->parent(); //debug() << "parent: " << parent; if ( parent && parent->parent() ) { int row = parent->parent()->childGroups().indexOf( parent ); return createIndex( row , 0, BookmarkViewItemPtr::staticCast( parent ) ); } else { return QModelIndex(); } } int BookmarkModel::rowCount( const QModelIndex & parent ) const { //DEBUG_BLOCK if ( parent.column() > 0 ) { // debug() << "bad column"; return 0; } if (!parent.isValid()) { //debug() << "top level item has" << m_root->childCount(); return m_root->childCount(); } BookmarkViewItemPtr item = m_viewItems.value( parent.internalId() ); //debug() << "row: " << parent.row(); //debug() << "address: " << item; //debug() << "count: " << item->childCount(); return item->childCount(); } int BookmarkModel::columnCount(const QModelIndex & /*parent*/) const { //name, command, url, description return 4; } Qt::ItemFlags BookmarkModel::flags( const QModelIndex & index ) const { if (!index.isValid()) return Qt::ItemIsEnabled | Qt::ItemIsDropEnabled; BookmarkViewItemPtr item = BookmarkViewItemPtr::staticCast( m_viewItems.value( index.internalId() ) ); - if ( typeid( * item ) == typeid( BookmarkGroup ) ) + if ( BookmarkGroupPtr::dynamicCast( item ) ) { if ( index.column() != Command ) return Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDropEnabled | Qt::ItemIsDragEnabled; else return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDropEnabled | Qt::ItemIsDragEnabled; } else { if ( index.column() != Command ) return Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled; else return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled; } } QVariant BookmarkModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { switch( section ) { case Name: return i18n("Name"); case Command: return i18n("Type"); case Url: return i18n("URL"); case Description: return i18n("Description"); default: return QVariant(); } } return QVariant(); } bool BookmarkModel::setData( const QModelIndex & index, const QVariant & value, int role ) { if (role != Qt::EditRole) return false; if ( index.column() == Command ) return false; BookmarkViewItemPtr item = m_viewItems.value( index.internalId() ); switch( index.column() ) { case Name: item->rename( value.toString() ); emit dataChanged( index, index ); break; case Url: { AmarokUrl * url = dynamic_cast( item.data() ); if ( url ) { debug() << "writing " << value.toString() << " as new url!"; url->initFromString( value.toString() ); url->saveToDb(); emit dataChanged( index, index ); } break; } case Description: { item->setDescription( value.toString() ); AmarokUrl * url = dynamic_cast( item.data() ); if ( url ) { url->saveToDb(); emit dataChanged( index, index ); } break; } } return true; } QStringList BookmarkModel::mimeTypes() const { DEBUG_BLOCK QStringList ret; ret << AmarokMimeData::BOOKMARKGROUP_MIME; ret << AmarokMimeData::AMAROKURL_MIME; return ret; } QMimeData* BookmarkModel::mimeData( const QModelIndexList &indexes ) const { DEBUG_BLOCK AmarokMimeData* mime = new AmarokMimeData(); BookmarkGroupList groups; BookmarkList bookmarks; foreach( const QModelIndex &index, indexes ) { BookmarkViewItemPtr item = m_viewItems.value( index.internalId() ); - if ( typeid( * item ) == typeid( BookmarkGroup ) ) { - BookmarkGroupPtr playlistGroup = BookmarkGroupPtr::staticCast( item ); - groups << playlistGroup; - } - else - { - AmarokUrlPtr bookmark = AmarokUrlPtr::dynamicCast( item ); - if( bookmark ) - bookmarks << bookmark; - } + if ( auto group = BookmarkGroupPtr::dynamicCast( item ) ) + groups << group; + else if( auto bookmark = AmarokUrlPtr::dynamicCast( item ) ) + bookmarks << bookmark; } debug() << "adding " << groups.count() << " groups and " << bookmarks.count() << " bookmarks"; mime->setBookmarkGroups( groups ); mime->setBookmarks( bookmarks ); return mime; } bool BookmarkModel::dropMimeData ( const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent ) //reimplemented { DEBUG_BLOCK Q_UNUSED( column ); Q_UNUSED( row ); if( action == Qt::IgnoreAction ) return true; BookmarkGroupPtr parentGroup; if ( !parent.isValid() ) { parentGroup = m_root; } else { parentGroup = BookmarkGroupPtr::staticCast( m_viewItems.value( parent.internalId() ) ); } if( data->hasFormat( AmarokMimeData::BOOKMARKGROUP_MIME ) ) { debug() << "Found playlist group mime type"; const AmarokMimeData* bookmarkGroupDrag = dynamic_cast( data ); if( bookmarkGroupDrag ) { BookmarkGroupList groups = bookmarkGroupDrag->bookmarkGroups(); foreach( BookmarkGroupPtr group, groups ) { group->reparent( parentGroup ); } reloadFromDb(); return true; } } else if( data->hasFormat( AmarokMimeData::AMAROKURL_MIME ) ) { debug() << "Found amarokurl mime type"; const AmarokMimeData* dragList = dynamic_cast( data ); if( dragList ) { BookmarkList bookmarks = dragList->bookmarks(); foreach( AmarokUrlPtr bookmarkPtr, bookmarks ) { bookmarkPtr->reparent( parentGroup ); } reloadFromDb(); return true; } } return false; } void BookmarkModel::createTables() { DEBUG_BLOCK auto sqlStorage = StorageManager::instance()->sqlStorage(); if( !sqlStorage ) return; sqlStorage->query( QString( "CREATE TABLE bookmark_groups (" " id " + sqlStorage->idType() + ", parent_id INTEGER" ", name " + sqlStorage->textColumnType() + ", description " + sqlStorage->textColumnType() + ", custom " + sqlStorage->textColumnType() + " ) ENGINE = MyISAM;" ) ); sqlStorage->query( QString( "CREATE TABLE bookmarks (" " id " + sqlStorage->idType() + ", parent_id INTEGER" ", name " + sqlStorage->textColumnType() + ", url " + sqlStorage->exactTextColumnType() + ", description " + sqlStorage->exactTextColumnType() + ", custom " + sqlStorage->textColumnType() + " ) ENGINE = MyISAM;" ) ); } void BookmarkModel::deleteTables() { DEBUG_BLOCK auto sqlStorage = StorageManager::instance()->sqlStorage(); if( !sqlStorage ) return; sqlStorage->query( "DROP TABLE IF EXISTS bookmark_groups;" ); sqlStorage->query( "DROP TABLE IF EXISTS bookmarks;" ); } void BookmarkModel::checkTables() { DEBUG_BLOCK auto sqlStorage = StorageManager::instance()->sqlStorage(); if( !sqlStorage ) return; QStringList values = sqlStorage->query( QString("SELECT version FROM admin WHERE component = '%1';").arg(sqlStorage->escape( key ) ) ); //also check if the db version is correct but the table is simply missing... can happen due to a bug in 2.2.0 beta1 and beta2 QStringList values2 = sqlStorage->query( "show tables like 'bookmarks';"); if( values.isEmpty() || values2.isEmpty() ) { debug() << "creating Playlist Tables"; createTables(); sqlStorage->query( "INSERT INTO admin(component,version) " "VALUES('" + key + "'," + QString::number( BOOKMARK_DB_VERSION ) + ");" ); } else if ( values.at( 0 ).toInt() < 4 ) { upgradeTables( values.at( 0 ).toInt() ); sqlStorage->query( "UPDATE admin SET version=" + QString::number( BOOKMARK_DB_VERSION ) + " WHERE component=" + key + ';' ); } } void BookmarkModel::reloadFromDb() { DEBUG_BLOCK; beginResetModel(); m_root->clear(); endResetModel(); } void BookmarkModel::editBookmark( int id ) { //for now, assume that the newly added playlist is in the top level: int row = m_root->childGroups().count() - 1; foreach ( AmarokUrlPtr bookmark, m_root->childBookmarks() ) { row++; if ( bookmark->id() == id ) { emit editIndex( createIndex( row , 0, BookmarkViewItemPtr::staticCast( bookmark ) ) ); } } } void BookmarkModel::createNewGroup() { DEBUG_BLOCK BookmarkGroup * group = new BookmarkGroup( i18n("New Group"), m_root ); group->save(); int id = group->id(); delete group; reloadFromDb(); int row = 0; foreach ( BookmarkGroupPtr childGroup, m_root->childGroups() ) { if ( childGroup->id() == id ) { debug() << "emitting edit for " << childGroup->name() << " id " << childGroup->id() << " in row " << row; emit editIndex( createIndex( row , 0, BookmarkViewItemPtr::staticCast( childGroup ) ) ); } row++; } } void BookmarkModel::createNewBookmark() { DEBUG_BLOCK AmarokUrl * url = new AmarokUrl(); url->reparent( m_root ); url->setName( i18n( "New Bookmark" ) ); url->setCommand( i18n( "none" ) ); url->saveToDb(); int id = url->id(); delete url; reloadFromDb(); debug() << "id of new bookmark: " << id; int row = m_root->childGroups().count(); foreach ( AmarokUrlPtr childBookmark, m_root->childBookmarks() ) { debug() << id << " == " << childBookmark->id() << " ? "; if ( childBookmark->id() == id ) { debug() << "emitting edit for " << childBookmark->name() << " id " << childBookmark->id() << " in row " << row; emit editIndex( createIndex( row , 0, BookmarkViewItemPtr::staticCast( childBookmark ) ) ); } row++; } } void BookmarkModel::setBookmarkArg( const QString &name, const QString &key, const QString &value ) { if( setBookmarkArgRecursively( m_root, name, key, value ) ) { reloadFromDb(); The::amarokUrlHandler()->updateTimecodes(); } else { warning() << "Cannot set argument" << key << "of the bookmark" << name << "to value" << value << "- bookmark not found."; } } void BookmarkModel::deleteBookmark( const QString& name ) { DEBUG_BLOCK debug() << "Name: " << name; if( deleteBookmarkRecursively( m_root, name ) ) { debug() << "Deleted!"; reloadFromDb(); The::amarokUrlHandler()->updateTimecodes(); } else debug() << "No such bookmark found!"; } void BookmarkModel::renameBookmark( const QString& oldName, const QString& newName ) { DEBUG_BLOCK debug() << "OldName: " << oldName << " NewName: " << newName; if( renameBookmarkRecursively( m_root, oldName, newName ) ) { debug() << "Renamed!!"; reloadFromDb(); const QString* name = &newName; The::amarokUrlHandler()->updateTimecodes( name ); } else debug() << "No such bookmark found!"; } bool BookmarkModel::setBookmarkArgRecursively( BookmarkGroupPtr group, const QString& name, const QString& key, const QString &value ) { foreach( AmarokUrlPtr item, group->childBookmarks() ) { if( item->name() == name ) { item->setArg( key, value ); item->saveToDb(); return true; } } //if not found, recurse through child groups foreach( BookmarkGroupPtr childGroup, group->childGroups() ) { if( setBookmarkArgRecursively( childGroup, name, key, value ) ) return true; } return false; } bool BookmarkModel::deleteBookmarkRecursively( BookmarkGroupPtr group, const QString& name ) { foreach( AmarokUrlPtr item, group->childBookmarks() ) { debug() << "item->name(): " << item->name(); if( item->name() == name ) { debug() << "Deleting Bookmark: " << name; item->removeFromDb(); return true; } } //if not found, recurse through child groups foreach( BookmarkGroupPtr childGroup, group->childGroups() ) { if( deleteBookmarkRecursively( childGroup, name ) ) return true; } return false; } bool BookmarkModel::renameBookmarkRecursively( BookmarkGroupPtr group, const QString& oldName, const QString& newName ) { foreach( AmarokUrlPtr item, group->childBookmarks() ) { debug() << "item->name(): " << item->name(); if( item->name() == oldName) { debug() << "Renaming Bookmark: " << oldName; item->rename(newName); return true; } } //if not found, recurse through child groups foreach( BookmarkGroupPtr childGroup, group->childGroups() ) { if( renameBookmarkRecursively( childGroup, oldName, newName ) ) return true; } return false; } void BookmarkModel::upgradeTables( int from ) { auto sqlStorage = StorageManager::instance()->sqlStorage(); if( !sqlStorage ) return; if ( from == 2 ) { sqlStorage->query( "ALTER TABLE bookmarks ADD custom " + sqlStorage->textColumnType() + ';' ); } sqlStorage->query( "ALTER TABLE bookmark_groups ADD custom " + sqlStorage->textColumnType() + ';' ); } diff --git a/src/amarokurls/BookmarkTreeView.cpp b/src/amarokurls/BookmarkTreeView.cpp index bcb54d0c7a..c3037e7a69 100644 --- a/src/amarokurls/BookmarkTreeView.cpp +++ b/src/amarokurls/BookmarkTreeView.cpp @@ -1,440 +1,435 @@ /**************************************************************************************** * 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 "BookmarkTreeView.h" #include "BookmarkModel.h" #include "dialogs/TagDialog.h" #include "PaletteHandler.h" #include "AmarokUrl.h" #include "AmarokUrlHandler.h" #include "BookmarkGroup.h" #include "playlist/PlaylistController.h" #include "SvgHandler.h" #include "core-impl/meta/timecode/TimecodeMeta.h" #include #include #include #include #include #include #include #include #include #include #include BookmarkTreeView::BookmarkTreeView( QWidget *parent ) : QTreeView( parent ) , m_loadAction( 0 ) , m_deleteAction( 0 ) , m_createTimecodeTrackAction( 0 ) , m_addGroupAction( 0 ) { setEditTriggers( QAbstractItemView::SelectedClicked ); setSelectionMode( QAbstractItemView::ExtendedSelection ); setDragEnabled( true ); setAcceptDrops( true ); setAlternatingRowColors( true ); setDropIndicatorShown( true ); connect( header(), &QHeaderView::sectionCountChanged, this, &BookmarkTreeView::slotSectionCountChanged ); } BookmarkTreeView::~BookmarkTreeView() { } void BookmarkTreeView::mouseDoubleClickEvent( QMouseEvent * event ) { QModelIndex index = m_proxyModel->mapToSource( indexAt( event->pos() ) ); if( index.isValid() ) { BookmarkViewItemPtr item = BookmarkModel::instance()->data( index, 0xf00d ).value(); - if ( typeid( *item ) == typeid( AmarokUrl ) ) { - AmarokUrl * bookmark = static_cast< AmarokUrl* >( item.data() ); + if ( auto bookmark = AmarokUrlPtr::dynamicCast( item ) ) bookmark->run(); - } } } void BookmarkTreeView::keyPressEvent( QKeyEvent *event ) { switch( event->key() ) { case Qt::Key_Delete: slotDelete(); return; case Qt::Key_F2: slotRename(); return; } QTreeView::keyPressEvent( event ); } QList BookmarkTreeView::createCommonActions( QModelIndexList indices ) { DEBUG_BLOCK //there are 4 columns, so for each selected row we get 4 indices... int selectedRowCount = indices.count() / 4; QList< QAction * > actions; if ( m_loadAction == 0 ) { m_loadAction = new QAction( QIcon::fromTheme( "folder-open" ), i18nc( "Load the view represented by this bookmark", "&Load" ), this ); connect( m_loadAction, &QAction::triggered, this, &BookmarkTreeView::slotLoad ); } if ( m_deleteAction == 0 ) { m_deleteAction = new QAction( QIcon::fromTheme( "media-track-remove-amarok" ), i18n( "&Delete" ), this ); connect( m_deleteAction, &QAction::triggered, this, &BookmarkTreeView::slotDelete ); } if ( m_createTimecodeTrackAction == 0 ) { debug() << "creating m_createTimecodeTrackAction"; m_createTimecodeTrackAction = new QAction( QIcon::fromTheme( "media-track-edit-amarok" ), i18n( "&Create timecode track" ), this ); connect( m_createTimecodeTrackAction, &QAction::triggered, this, &BookmarkTreeView::slotCreateTimecodeTrack ); } if ( selectedRowCount == 1 ) actions << m_loadAction; if ( selectedRowCount > 0 ) actions << m_deleteAction; if ( selectedRowCount == 2 ) { debug() << "adding m_createTimecodeTrackAction"; actions << m_createTimecodeTrackAction; } return actions; } void BookmarkTreeView::slotLoad() { DEBUG_BLOCK foreach( BookmarkViewItemPtr item, selectedItems() ) { - if( typeid( * item ) == typeid( AmarokUrl ) ) - { - AmarokUrlPtr bookmark = AmarokUrlPtr::staticCast( item ); + if( auto bookmark = AmarokUrlPtr::dynamicCast( item ) ) bookmark->run(); - } } } void BookmarkTreeView::slotDelete() { DEBUG_BLOCK //TODO FIXME Confirmation of delete foreach( BookmarkViewItemPtr item, selectedItems() ) { debug() << "deleting " << item->name(); item->removeFromDb(); item->parent()->deleteChild( item ); } BookmarkModel::instance()->reloadFromDb(); The::amarokUrlHandler()->updateTimecodes(); } void BookmarkTreeView::slotRename() { DEBUG_BLOCK if ( selectionModel()->hasSelection() ) edit( selectionModel()->selectedIndexes().first() ); } void BookmarkTreeView::contextMenuEvent( QContextMenuEvent * event ) { DEBUG_BLOCK QModelIndexList indices = selectionModel()->selectedIndexes(); QMenu* menu = new QMenu( this ); QList actions = createCommonActions( indices ); foreach( QAction * action, actions ) menu->addAction( action ); if( indices.count() == 0 ) menu->addAction( m_addGroupAction ); menu->exec( event->globalPos() ); } void BookmarkTreeView::resizeEvent( QResizeEvent *event ) { QHeaderView *headerView = header(); const int oldWidth = event->oldSize().width(); const int newWidth = event->size().width(); if( oldWidth == newWidth || oldWidth < 0 || newWidth < 0 ) return; disconnect( headerView, &QHeaderView::sectionResized, this, &BookmarkTreeView::slotSectionResized ); QMap::const_iterator i = m_columnsSize.constBegin(); while( i != m_columnsSize.constEnd() ) { const BookmarkModel::Column col = i.key(); if( col != BookmarkModel::Command && col != BookmarkModel::Description ) headerView->resizeSection( col, static_cast( i.value() * newWidth ) ); ++i; } connect( headerView, &QHeaderView::sectionResized, this, &BookmarkTreeView::slotSectionResized ); QWidget::resizeEvent( event ); } bool BookmarkTreeView::viewportEvent( QEvent *event ) { if( event->type() == QEvent::ToolTip ) { QHelpEvent *he = static_cast( event ); QModelIndex idx = indexAt( he->pos() ); if( idx.isValid() ) { QRect vr = visualRect( idx ); QSize shr = itemDelegate( idx )->sizeHint( viewOptions(), idx ); if( shr.width() > vr.width() ) QToolTip::showText( he->globalPos(), idx.data( Qt::DisplayRole ).toString() ); } else { QToolTip::hideText(); event->ignore(); } return true; } return QTreeView::viewportEvent( event ); } QSet BookmarkTreeView::selectedItems() const { DEBUG_BLOCK QSet selected; foreach( const QModelIndex &index, selectionModel()->selectedIndexes() ) { QModelIndex sourceIndex = m_proxyModel->mapToSource( index ); if( sourceIndex.isValid() && sourceIndex.internalPointer() && sourceIndex.column() == 0 ) { debug() << "inserting item " << sourceIndex.data( Qt::DisplayRole ).toString(); selected.insert( BookmarkModel::instance()->data( sourceIndex, 0xf00d ).value() ); } } return selected; } void BookmarkTreeView::setNewGroupAction( QAction * action ) { m_addGroupAction = action; } void BookmarkTreeView::selectionChanged( const QItemSelection & selected, const QItemSelection & deselected ) { DEBUG_BLOCK Q_UNUSED( deselected ) QModelIndexList indexes = selected.indexes(); debug() << indexes.size() << " items selected"; foreach( const QModelIndex &index, indexes ) { const QModelIndex sourceIndex = m_proxyModel->mapToSource( index ); if( sourceIndex.column() == 0 ) { BookmarkViewItemPtr item = BookmarkModel::instance()->data( sourceIndex, 0xf00d ).value(); - if ( typeid( * item ) == typeid( AmarokUrl ) ) { + if ( auto bookmark = AmarokUrlPtr::dynamicCast( item ) ) + { debug() << "a url was selected..."; - AmarokUrl bookmark = *static_cast< AmarokUrl* >( item.data() ); - emit( bookmarkSelected( bookmark ) ); + emit( bookmarkSelected( *bookmark ) ); } } } } QMenu* BookmarkTreeView::contextMenu( const QPoint& point ) { DEBUG_BLOCK QMenu* menu = new QMenu( 0 ); debug() << "getting menu for point:" << point; QModelIndex index = m_proxyModel->mapToSource( indexAt( point ) ); if( index.isValid() ) { debug() << "got valid index"; QModelIndexList indices = selectionModel()->selectedIndexes(); QList actions = createCommonActions( indices ); foreach( QAction * action, actions ) menu->addAction( action ); if( indices.count() == 0 ) menu->addAction( m_addGroupAction ); } return menu; } void BookmarkTreeView::slotCreateTimecodeTrack() const { //TODO: Factor into separate class QList list = selectedItems().toList(); if ( list.count() != 2 ) return; const AmarokUrl * url1 = dynamic_cast( list.at( 0 ).data() ); if ( url1 == 0 ) return; if ( url1->command() != "play" ) return; const AmarokUrl * url2 = dynamic_cast( list.at( 1 ).data() ); if ( url2 == 0 ) return; if ( url2->command() != "play" ) return; if ( url1->path() != url2->path() ) return; //ok, so we actually have to timecodes from the same base url, not get the //minimum and maximum time: qreal pos1 = 0; qreal pos2 = 0; if ( url1->args().keys().contains( "pos" ) ) { pos1 = url1->args().value( "pos" ).toDouble(); } if ( url2->args().keys().contains( "pos" ) ) { pos2 = url2->args().value( "pos" ).toDouble(); } if ( pos1 == pos2 ) return; qint64 start = qMin( pos1, pos2 ) * 1000; qint64 end = qMax( pos1, pos2 ) * 1000; //Now we really should pop up a menu to get the user to enter some info about this //new track, but for now, just fake it as this is just for testing anyway QUrl url = QUrl::fromEncoded ( QByteArray::fromBase64 ( url1->path().toUtf8() ) ); Meta::TimecodeTrackPtr track = Meta::TimecodeTrackPtr( new Meta::TimecodeTrack( i18n( "New Timecode Track" ), url, start, end ) ); Meta::TimecodeAlbumPtr album = Meta::TimecodeAlbumPtr( new Meta::TimecodeAlbum( i18n( "Unknown" ) ) ); Meta::TimecodeArtistPtr artist = Meta::TimecodeArtistPtr( new Meta::TimecodeArtist( i18n( "Unknown" ) ) ); Meta::TimecodeGenrePtr genre = Meta::TimecodeGenrePtr( new Meta::TimecodeGenre( i18n( "Unknown" ) ) ); album->addTrack( track ); artist->addTrack( track ); genre->addTrack( track ); track->setAlbum( album ); track->setArtist( artist ); track->setGenre( genre ); album->setAlbumArtist( artist ); //make the user give us some info about this item... Meta::TrackList tl; tl.append( Meta::TrackPtr::staticCast( track ) ); TagDialog *dialog = new TagDialog( tl, 0 ); dialog->show(); //now add it to the playlist The::playlistController()->insertOptioned( Meta::TrackPtr::staticCast( track ) ); } void BookmarkTreeView::setProxy( QSortFilterProxyModel *proxy ) { m_proxyModel = proxy; } void BookmarkTreeView::slotEdit( const QModelIndex &index ) { //translate to proxy terms edit( m_proxyModel->mapFromSource( index ) ); } void BookmarkTreeView::slotSectionResized( int logicalIndex, int oldSize, int newSize ) { Q_UNUSED( oldSize ) BookmarkModel::Column col = BookmarkModel::Column( logicalIndex ); m_columnsSize[ col ] = static_cast( newSize ) / header()->length(); } void BookmarkTreeView::slotSectionCountChanged( int oldCount, int newCount ) { Q_UNUSED( oldCount ) const QHeaderView *headerView = header(); for( int i = 0; i < newCount; ++i ) { const int index = headerView->logicalIndex( i ); const int width = columnWidth( index ); const qreal ratio = static_cast( width ) / headerView->length(); const BookmarkModel::Column col = BookmarkModel::Column( index ); if( col == BookmarkModel::Command ) header()->setSectionResizeMode( index, QHeaderView::ResizeToContents ); m_columnsSize[ col ] = ratio; } } diff --git a/src/browsers/CollectionSortFilterProxyModel.cpp b/src/browsers/CollectionSortFilterProxyModel.cpp index 30fafa5526..b7fe783b5f 100644 --- a/src/browsers/CollectionSortFilterProxyModel.cpp +++ b/src/browsers/CollectionSortFilterProxyModel.cpp @@ -1,209 +1,210 @@ /**************************************************************************************** * Copyright (c) 2007 Nikolaj Hald Nielsen * * Copyright (c) 2008 Seb Ruiz * * 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 . * ****************************************************************************************/ #include "CollectionSortFilterProxyModel.h" #include "amarokconfig.h" #include "browsers/CollectionTreeItem.h" #include "core/meta/Meta.h" #include "core/support/Debug.h" #include "widgets/PrettyTreeRoles.h" #include #include #include CollectionSortFilterProxyModel::CollectionSortFilterProxyModel( QObject * parent ) : QSortFilterProxyModel( parent ) , m_col( new QCollator ) { setSortLocaleAware( true ); setSortRole( PrettyTreeRoles::SortRole ); setFilterRole( PrettyTreeRoles::FilterRole ); setSortCaseSensitivity( Qt::CaseInsensitive ); setFilterCaseSensitivity( Qt::CaseInsensitive ); setDynamicSortFilter( true ); m_col->setCaseSensitivity( Qt::CaseInsensitive ); } CollectionSortFilterProxyModel::~CollectionSortFilterProxyModel() { delete m_col; } bool CollectionSortFilterProxyModel::hasChildren(const QModelIndex & parent) const { QModelIndex sourceParent = mapToSource(parent); return sourceModel()->hasChildren(sourceParent); } +bool +CollectionSortFilterProxyModel::filterAcceptsRow( int source_row, const QModelIndex& source_parent ) const +{ + bool stringAccepted = QSortFilterProxyModel::filterAcceptsRow( source_row, source_parent ); + + if( AmarokConfig::showYears()) + { + QModelIndex index = sourceModel()->index( source_row, 0, source_parent ); + if( treeItem( index )->isAlbumItem() ) + { + bool yearLoaded = index.data( PrettyTreeRoles::YearRole ) >= 0; + return yearLoaded && stringAccepted; + } + } + + return stringAccepted; +} + bool CollectionSortFilterProxyModel::lessThan( const QModelIndex &left, const QModelIndex &right ) const { CollectionTreeItem *leftItem = treeItem( left ); CollectionTreeItem *rightItem = treeItem( right ); // various artists and no label items are always at the top if( !leftItem || leftItem->isVariousArtistItem() || leftItem->isNoLabelItem() ) return true; if( !rightItem || rightItem->isVariousArtistItem() || rightItem->isNoLabelItem() ) return false; if( leftItem->isTrackItem() && rightItem->isTrackItem() ) return lessThanTrack( left, right ); if( leftItem->isAlbumItem() && rightItem->isAlbumItem() ) return lessThanAlbum( left, right ); if( leftItem->isDataItem() && rightItem->isDataItem() ) return lessThanItem( left, right ); return QSortFilterProxyModel::lessThan( left, right ); } bool CollectionSortFilterProxyModel::lessThanTrack( const QModelIndex &left, const QModelIndex &right ) const { const Meta::TrackPtr leftTrack = Meta::TrackPtr::dynamicCast( treeItem(left)->data() ); const Meta::TrackPtr rightTrack = Meta::TrackPtr::dynamicCast( treeItem(right)->data() ); if( !leftTrack || !rightTrack ) { DEBUG_BLOCK error() << "Should never have compared these two indexes" << left.data(Qt::DisplayRole) << "and" << right.data(Qt::DisplayRole); return QSortFilterProxyModel::lessThan( left, right ); } if( AmarokConfig::showTrackNumbers() ) { //First compare by disc number if ( leftTrack->discNumber() < rightTrack->discNumber() ) return true; if ( leftTrack->discNumber() > rightTrack->discNumber() ) return false; //Disc #'s are equal, compare by track number if( leftTrack->trackNumber() < rightTrack->trackNumber() ) return true; if( leftTrack->trackNumber() > rightTrack->trackNumber() ) return false; } // compare by name { int comp = m_col->compare( leftTrack->sortableName(), rightTrack->sortableName() ); if( comp < 0 ) return true; if( comp > 0 ) return false; } return leftTrack.data() < rightTrack.data(); // prevent expanded tracks from switching places (if that ever happens) } bool CollectionSortFilterProxyModel::lessThanAlbum( const QModelIndex &left, const QModelIndex &right ) const { Meta::AlbumPtr leftAlbum = Meta::AlbumPtr::dynamicCast( treeItem(left)->data() ); Meta::AlbumPtr rightAlbum = Meta::AlbumPtr::dynamicCast( treeItem(right)->data() ); if( !leftAlbum || !rightAlbum ) { DEBUG_BLOCK error() << "Should never have compared these two indexes" << left.data(Qt::DisplayRole) << "and" << right.data(Qt::DisplayRole); return QSortFilterProxyModel::lessThan( left, right ); } // compare by year if( AmarokConfig::showYears() ) { - int leftYear = albumYear( leftAlbum ); - int rightYear = albumYear( rightAlbum ); + int leftYear = left.data( PrettyTreeRoles::YearRole ).toInt(); + int rightYear = right.data( PrettyTreeRoles::YearRole ).toInt(); if( leftYear < rightYear ) return false; // left album is newer if( leftYear > rightYear ) return true; } // compare by name { int comp = m_col->compare( leftAlbum->sortableName(), rightAlbum->sortableName() ); if( comp < 0 ) return true; if( comp > 0 ) return false; } return leftAlbum.data() < rightAlbum.data(); // prevent expanded albums from switching places } bool CollectionSortFilterProxyModel::lessThanItem( const QModelIndex &left, const QModelIndex &right ) const { - Meta::DataPtr leftData = Meta::DataPtr::dynamicCast( treeItem(left)->data() ); - Meta::DataPtr rightData = Meta::DataPtr::dynamicCast( treeItem(right)->data() ); + Meta::DataPtr leftData = treeItem(left)->data(); + Meta::DataPtr rightData = treeItem(right)->data(); if( !leftData || !rightData ) { DEBUG_BLOCK error() << "Should never have compared these two indexes" << left.data(Qt::DisplayRole) << "and" << right.data(Qt::DisplayRole); return QSortFilterProxyModel::lessThan( left, right ); } // compare by name { int comp = m_col->compare( leftData->sortableName(), rightData->sortableName() ); if( comp < 0 ) return true; if( comp > 0 ) return false; } return leftData.data() < rightData.data(); // prevent expanded data from switching places } inline CollectionTreeItem* CollectionSortFilterProxyModel::treeItem( const QModelIndex &index ) const { return static_cast( index.internalPointer() ); } - -int -CollectionSortFilterProxyModel::albumYear( Meta::AlbumPtr album ) const -{ - if( album->name().isEmpty() ) // an unnamed album has no year - return 0; - - Meta::TrackList tracks = album->tracks(); - if( !tracks.isEmpty() ) - { - Meta::YearPtr year = tracks.first()->year(); - if( year && (year->year() != 0) ) - return year->year(); - } - return 0; -} - diff --git a/src/browsers/CollectionSortFilterProxyModel.h b/src/browsers/CollectionSortFilterProxyModel.h index 096fe04a8f..e4fea2a9a8 100644 --- a/src/browsers/CollectionSortFilterProxyModel.h +++ b/src/browsers/CollectionSortFilterProxyModel.h @@ -1,59 +1,58 @@ /**************************************************************************************** * Copyright (c) 2007 Nikolaj Hald Nielsen * * Copyright (c) 2008 Seb Ruiz * * Copyright (c) 2013 Ralf Engels * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #ifndef COLLECTIONSORTFILTERPROXYMODEL_H #define COLLECTIONSORTFILTERPROXYMODEL_H #include "core/meta/forward_declarations.h" #include class CollectionTreeItem; class QCollator; /** This is a custom QSortFilterProxyModel that gives special sort orders for our meta objects. e.g. it sorts tracks by disc number and track number. @author Nikolaj Hald Nielsen */ class CollectionSortFilterProxyModel : public QSortFilterProxyModel { public: - explicit CollectionSortFilterProxyModel( QObject * parent = 0 ); + explicit CollectionSortFilterProxyModel( QObject *parent = nullptr ); virtual ~CollectionSortFilterProxyModel(); - bool hasChildren(const QModelIndex &parent) const; + virtual bool hasChildren(const QModelIndex &parent) const override; protected: - virtual bool lessThan( const QModelIndex &left, const QModelIndex &right ) const; + virtual bool lessThan( const QModelIndex &left, const QModelIndex &right ) const override; + virtual bool filterAcceptsRow( int source_row, const QModelIndex & source_parent ) const override; private: QCollator *m_col; - /** Tries to compute a year for the album using the track years. */ - int albumYear( Meta::AlbumPtr album ) const; CollectionTreeItem* treeItem( const QModelIndex &index ) const; bool lessThanTrack( const QModelIndex &left, const QModelIndex &right ) const; bool lessThanAlbum( const QModelIndex &left, const QModelIndex &right ) const; bool lessThanItem( const QModelIndex &left, const QModelIndex &right ) const; }; #endif diff --git a/src/browsers/CollectionTreeItemModelBase.cpp b/src/browsers/CollectionTreeItemModelBase.cpp index fedd3d9714..60587b3179 100644 --- a/src/browsers/CollectionTreeItemModelBase.cpp +++ b/src/browsers/CollectionTreeItemModelBase.cpp @@ -1,1214 +1,1314 @@ /**************************************************************************************** * Copyright (c) 2007 Alexandre Pereira de Oliveira * * Copyright (c) 2007-2009 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 "CollectionTreeItemModelBase" #include "CollectionTreeItemModelBase.h" #include "AmarokMimeData.h" #include "FileType.h" #include "SvgHandler.h" #include "amarokconfig.h" #include "browsers/CollectionTreeItem.h" #include "core/collections/Collection.h" #include "core/collections/QueryMaker.h" #include "core/meta/TrackEditor.h" #include "core/meta/support/MetaConstants.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include "core-impl/collections/support/TextualQueryFilter.h" #include "widgets/PrettyTreeRoles.h" #include +#include +#include #include #include +#include #include #include #include #include #include using namespace Meta; +class TrackLoaderJob : public ThreadWeaver::Job +{ +public: + TrackLoaderJob( const QModelIndex &index, const Meta::AlbumPtr &album, CollectionTreeItemModelBase *model ) + : m_index( index ) + , m_album( album ) + , m_model( model ) + { + } + +protected: + void run( ThreadWeaver::JobPointer self, ThreadWeaver::Thread *thread ) override + { + Q_UNUSED( self ) + Q_UNUSED( thread ) + + m_model->tracksLoaded( m_album, m_index, m_album->tracks() ); + } + +private: + QModelIndex m_index; + Meta::AlbumPtr m_album; + CollectionTreeItemModelBase *m_model; +}; + inline uint qHash( const Meta::DataPtr &data ) { return qHash( data.data() ); } /** * This set determines which collection browser levels should have shown Various Artists * item under them. AlbumArtist is certain, (Track)Artist is questionable. */ static const QSet variousArtistCategories = QSet() << CategoryId::AlbumArtist; CollectionTreeItemModelBase::CollectionTreeItemModelBase( ) : QAbstractItemModel() + , m_loadingAlbumsMutex( new QMutex ) , m_rootItem( 0 ) , m_animFrame( 0 ) , m_loading1( QPixmap( QStandardPaths::locate( QStandardPaths::GenericDataLocation, "amarok/images/loading1.png" ) ) ) , m_loading2( QPixmap( QStandardPaths::locate( QStandardPaths::GenericDataLocation, "amarok/images/loading2.png" ) ) ) , m_currentAnimPixmap( m_loading1 ) , m_autoExpand( false ) { m_timeLine = new QTimeLine( 10000, this ); m_timeLine->setFrameRange( 0, 20 ); m_timeLine->setLoopCount ( 0 ); connect( m_timeLine, &QTimeLine::frameChanged, this, &CollectionTreeItemModelBase::loadingAnimationTick ); } CollectionTreeItemModelBase::~CollectionTreeItemModelBase() { KConfigGroup config = Amarok::config( "Collection Browser" ); QList levelNumbers; foreach( CategoryId::CatMenuId category, levels() ) levelNumbers.append( category ); config.writeEntry( "TreeCategory", levelNumbers ); if( m_rootItem ) m_rootItem->deleteLater(); + + delete m_loadingAlbumsMutex; } Qt::ItemFlags CollectionTreeItemModelBase::flags(const QModelIndex & index) const { Qt::ItemFlags flags = 0; if( index.isValid() ) { flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEditable; } return flags; } bool CollectionTreeItemModelBase::setData( const QModelIndex &index, const QVariant &value, int role ) { Q_UNUSED( role ) if( !index.isValid() ) return false; CollectionTreeItem *item = static_cast( index.internalPointer() ); Meta::DataPtr data = item->data(); if( Meta::TrackPtr track = Meta::TrackPtr::dynamicCast( data ) ) { Meta::TrackEditorPtr ec = track->editor(); if( ec ) { ec->setTitle( value.toString() ); emit dataChanged( index, index ); return true; } } else if( Meta::AlbumPtr album = Meta::AlbumPtr::dynamicCast( data ) ) { Meta::TrackList tracks = album->tracks(); if( !tracks.isEmpty() ) { foreach( Meta::TrackPtr track, tracks ) { Meta::TrackEditorPtr ec = track->editor(); if( ec ) ec->setAlbum( value.toString() ); } emit dataChanged( index, index ); return true; } } else if( Meta::ArtistPtr artist = Meta::ArtistPtr::dynamicCast( data ) ) { Meta::TrackList tracks = artist->tracks(); if( !tracks.isEmpty() ) { foreach( Meta::TrackPtr track, tracks ) { Meta::TrackEditorPtr ec = track->editor(); if( ec ) ec->setArtist( value.toString() ); } emit dataChanged( index, index ); return true; } } else if( Meta::GenrePtr genre = Meta::GenrePtr::dynamicCast( data ) ) { Meta::TrackList tracks = genre->tracks(); if( !tracks.isEmpty() ) { foreach( Meta::TrackPtr track, tracks ) { Meta::TrackEditorPtr ec = track->editor(); if( ec ) ec->setGenre( value.toString() ); } emit dataChanged( index, index ); return true; } } else if( Meta::YearPtr year = Meta::YearPtr::dynamicCast( data ) ) { Meta::TrackList tracks = year->tracks(); if( !tracks.isEmpty() ) { foreach( Meta::TrackPtr track, tracks ) { Meta::TrackEditorPtr ec = track->editor(); if( ec ) ec->setYear( value.toInt() ); } emit dataChanged( index, index ); return true; } } else if( Meta::ComposerPtr composer = Meta::ComposerPtr::dynamicCast( data ) ) { Meta::TrackList tracks = composer->tracks(); if( !tracks.isEmpty() ) { foreach( Meta::TrackPtr track, tracks ) { Meta::TrackEditorPtr ec = track->editor(); if( ec ) ec->setComposer( value.toString() ); } emit dataChanged( index, index ); return true; } } return false; } QVariant CollectionTreeItemModelBase::dataForItem( CollectionTreeItem *item, int role, int level ) const { if( level == -1 ) level = item->level(); if( item->isTrackItem() ) { Meta::TrackPtr track = Meta::TrackPtr::dynamicCast( item->data() ); switch( role ) { case Qt::DisplayRole: case Qt::ToolTipRole: case PrettyTreeRoles::FilterRole: { QString name = track->prettyName(); Meta::AlbumPtr album = track->album(); Meta::ArtistPtr artist = track->artist(); if( album && artist && album->isCompilation() ) name.prepend( QString("%1 - ").arg(artist->prettyName()) ); if( AmarokConfig::showTrackNumbers() ) { int trackNum = track->trackNumber(); if( trackNum > 0 ) name.prepend( QString("%1 - ").arg(trackNum) ); } // Check empty after track logic and before album logic if( name.isEmpty() ) name = i18nc( "The Name is not known", "Unknown" ); return name; } case Qt::DecorationRole: return QIcon::fromTheme( "media-album-track" ); case PrettyTreeRoles::SortRole: return track->sortableName(); } } else if( item->isAlbumItem() ) { Meta::AlbumPtr album = Meta::AlbumPtr::dynamicCast( item->data() ); switch( role ) { case Qt::DisplayRole: case Qt::ToolTipRole: { QString name = album->prettyName(); // add years for named albums (if enabled) - if( AmarokConfig::showYears() && !album->name().isEmpty() ) + if( AmarokConfig::showYears() ) { - Meta::TrackList tracks = album->tracks(); - if( !tracks.isEmpty() ) + if( m_years.contains( album.data() ) ) { - Meta::YearPtr year = tracks.first()->year(); - if( year && (year->year() != 0) ) - name.prepend( QString("%1 - ").arg( year->name() ) ); + int year = m_years.value( album.data() ); + + if( year > 0 ) + name.prepend( QString("%1 - ").arg( year ) ); + } + else if( !album->name().isEmpty() ) + { + QMutexLocker locker( m_loadingAlbumsMutex ); + if( !m_loadingAlbums.contains( album ) ) + { + m_loadingAlbums.insert( album ); + + auto nonConstThis = const_cast( this ); + auto job = QSharedPointer::create( itemIndex( item ), album, nonConstThis ); + ThreadWeaver::Queue::instance()->enqueue( job ); + } } } return name; } case Qt::DecorationRole: if( AmarokConfig::showAlbumArt() ) { QStyle *style = QApplication::style(); const int largeIconSize = style->pixelMetric( QStyle::PM_LargeIconSize ); return The::svgHandler()->imageWithBorder( album, largeIconSize, 2 ); } else return iconForLevel( level ); case PrettyTreeRoles::SortRole: return album->sortableName(); case PrettyTreeRoles::HasCoverRole: return AmarokConfig::showAlbumArt(); + + case PrettyTreeRoles::YearRole: + { + if( m_years.contains( album.data() ) ) + return m_years.value( album.data() ); + + else if( !album->name().isEmpty() ) + { + QMutexLocker locker( m_loadingAlbumsMutex ); + if( !m_loadingAlbums.contains( album ) ) + { + m_loadingAlbums.insert( album ); + + auto nonConstThis = const_cast( this ); + auto job = QSharedPointer::create( itemIndex( item ), album, nonConstThis ); + ThreadWeaver::Queue::instance()->enqueue( job ); + } + } + return -1; + } } } else if( item->isDataItem() ) { switch( role ) { case Qt::DisplayRole: case Qt::ToolTipRole: case PrettyTreeRoles::FilterRole: { QString name = item->data()->prettyName(); if( name.isEmpty() ) name = i18nc( "The Name is not known", "Unknown" ); return name; } case Qt::DecorationRole: { if( m_childQueries.values().contains( item ) ) { if( level < m_levelType.count() ) return m_currentAnimPixmap; } return iconForLevel( level ); } case PrettyTreeRoles::SortRole: return item->data()->sortableName(); } } else if( item->isVariousArtistItem() ) { switch( role ) { case Qt::DecorationRole: return QIcon::fromTheme( "similarartists-amarok" ); case Qt::DisplayRole: return i18n( "Various Artists" ); case PrettyTreeRoles::SortRole: return QString(); // so that we can compare it against other strings } } // -- all other roles are handled by item return item->data( role ); } QVariant CollectionTreeItemModelBase::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { if (section == 0) return m_headerText; } return QVariant(); } QModelIndex CollectionTreeItemModelBase::index(int row, int column, const QModelIndex & parent) const { //ensure sanity of parameters //we are a tree model, there are no columns if( row < 0 || column != 0 ) return QModelIndex(); CollectionTreeItem *parentItem; if (!parent.isValid()) parentItem = m_rootItem; else parentItem = static_cast(parent.internalPointer()); CollectionTreeItem *childItem = parentItem->child(row); if( childItem ) return createIndex(row, column, childItem); else return QModelIndex(); } QModelIndex CollectionTreeItemModelBase::parent(const QModelIndex & index) const { if( !index.isValid() ) return QModelIndex(); CollectionTreeItem *childItem = static_cast(index.internalPointer()); CollectionTreeItem *parentItem = childItem->parent(); return itemIndex( parentItem ); } int CollectionTreeItemModelBase::rowCount(const QModelIndex & parent) const { CollectionTreeItem *parentItem; if( !parent.isValid() ) parentItem = m_rootItem; else parentItem = static_cast(parent.internalPointer()); return parentItem->childCount(); } int CollectionTreeItemModelBase::columnCount(const QModelIndex & parent) const { Q_UNUSED( parent ) return 1; } QStringList CollectionTreeItemModelBase::mimeTypes() const { QStringList types; types << AmarokMimeData::TRACK_MIME; return types; } QMimeData* CollectionTreeItemModelBase::mimeData( const QModelIndexList &indices ) const { if ( indices.isEmpty() ) return 0; // first, filter out duplicate entries that may arise when both parent and child are selected QSet indexSet = QSet::fromList( indices ); QMutableSetIterator it( indexSet ); while( it.hasNext() ) { it.next(); // we go up in parent hierarchy searching whether some parent indices are already in set QModelIndex parentIndex = it.value(); while( parentIndex.isValid() ) // leave the root (top, invalid) index intact { parentIndex = parentIndex.parent(); // yes, we start from the parent of current index if( indexSet.contains( parentIndex ) ) { it.remove(); // parent already in selected set, remove child break; // no need to continue inner loop, already deleted } } } QList items; foreach( const QModelIndex &index, indexSet ) { if( index.isValid() ) items << static_cast( index.internalPointer() ); } return mimeData( items ); } QMimeData* CollectionTreeItemModelBase::mimeData( const QList &items ) const { if ( items.isEmpty() ) return 0; Meta::TrackList tracks; QList queries; foreach( CollectionTreeItem *item, items ) { if( item->allDescendentTracksLoaded() ) { tracks << item->descendentTracks(); } else { Collections::QueryMaker *qm = item->queryMaker(); for( CollectionTreeItem *tmp = item; tmp; tmp = tmp->parent() ) tmp->addMatch( qm, levelCategory( tmp->level() - 1 ) ); Collections::addTextualFilter( qm, m_currentFilter ); queries.append( qm ); } } qStableSort( tracks.begin(), tracks.end(), Meta::Track::lessThan ); AmarokMimeData *mimeData = new AmarokMimeData(); mimeData->setTracks( tracks ); mimeData->setQueryMakers( queries ); mimeData->startQueries(); return mimeData; } bool CollectionTreeItemModelBase::hasChildren ( const QModelIndex & parent ) const { if( !parent.isValid() ) return true; // must be root item! CollectionTreeItem *item = static_cast(parent.internalPointer()); //we added the collection level so we have to be careful with the item level return !item->isDataItem() || item->level() + levelModifier() <= m_levelType.count(); } void CollectionTreeItemModelBase::ensureChildrenLoaded( CollectionTreeItem *item ) { //only start a query if necessary and we are not querying for the item's children already if ( item->requiresUpdate() && !m_runningQueries.contains( item ) ) { listForLevel( item->level() + levelModifier(), item->queryMaker(), item ); } } CollectionTreeItem * CollectionTreeItemModelBase::treeItem( const QModelIndex &index ) const { if( !index.isValid() || index.model() != this ) return 0; return static_cast( index.internalPointer() ); } QModelIndex CollectionTreeItemModelBase::itemIndex( CollectionTreeItem *item ) const { if( !item || item == m_rootItem ) return QModelIndex(); return createIndex( item->row(), 0, item ); } -void CollectionTreeItemModelBase::listForLevel(int level, Collections::QueryMaker * qm, CollectionTreeItem * parent) +void +CollectionTreeItemModelBase::listForLevel(int level, Collections::QueryMaker * qm, CollectionTreeItem * parent) { if( qm && parent ) { // this check should not hurt anyone... needs to check if single... needs it if( m_runningQueries.contains( parent ) ) return; // following special cases are handled extra - right when the parent is added if( level > m_levelType.count() || parent->isVariousArtistItem() || parent->isNoLabelItem() ) { qm->deleteLater(); return; } // - the last level are always the tracks if ( level == m_levelType.count() ) qm->setQueryType( Collections::QueryMaker::Track ); // - all other levels are more complicate else { Collections::QueryMaker::QueryType nextLevel; if( level + 1 >= m_levelType.count() ) nextLevel = Collections::QueryMaker::Track; else nextLevel = mapCategoryToQueryType( m_levelType.value( level + 1 ) ); qm->setQueryType( mapCategoryToQueryType( m_levelType.value( level ) ) ); CategoryId::CatMenuId category = m_levelType.value( level ); if( category == CategoryId::Album ) { // restrict query to normal albums if the previous level // was the AlbumArtist category. In that case we handle compilations below if( levelCategory( level - 1 ) == CategoryId::AlbumArtist ) qm->setAlbumQueryMode( Collections::QueryMaker::OnlyNormalAlbums ); } else if( variousArtistCategories.contains( category ) ) // we used to handleCompilations() only if nextLevel is Album, but I cannot // tell any reason why we should have done this --- strohel handleCompilations( nextLevel, parent ); else if( category == CategoryId::Label ) handleTracksWithoutLabels( nextLevel, parent ); } for( CollectionTreeItem *tmp = parent; tmp; tmp = tmp->parent() ) tmp->addMatch( qm, levelCategory( tmp->level() - 1 ) ); Collections::addTextualFilter( qm, m_currentFilter ); addQueryMaker( parent, qm ); m_childQueries.insert( qm, parent ); qm->run(); //some very quick queries may be done so fast that the loading //animation creates an unnecessary flicker, therefore delay it for a bit QTimer::singleShot( 150, this, &CollectionTreeItemModelBase::startAnimationTick ); } } void CollectionTreeItemModelBase::setLevels( const QList &levelType ) { if( m_levelType == levelType ) return; m_levelType = levelType; updateHeaderText(); m_expandedItems.clear(); m_expandedSpecialNodes.clear(); m_runningQueries.clear(); m_childQueries.clear(); m_compilationQueries.clear(); filterChildren(); } Collections::QueryMaker::QueryType CollectionTreeItemModelBase::mapCategoryToQueryType( int levelType ) const { Collections::QueryMaker::QueryType type; switch( levelType ) { case CategoryId::Album: type = Collections::QueryMaker::Album; break; case CategoryId::Artist: type = Collections::QueryMaker::Artist; break; case CategoryId::AlbumArtist: type = Collections::QueryMaker::AlbumArtist; break; case CategoryId::Composer: type = Collections::QueryMaker::Composer; break; case CategoryId::Genre: type = Collections::QueryMaker::Genre; break; case CategoryId::Label: type = Collections::QueryMaker::Label; break; case CategoryId::Year: type = Collections::QueryMaker::Year; break; default: type = Collections::QueryMaker::None; break; } return type; } +void +CollectionTreeItemModelBase::tracksLoaded( Meta::AlbumPtr album, const QModelIndex &index, const Meta::TrackList& tracks ) +{ + DEBUG_BLOCK + + if( !album ) + return; + + QMutexLocker locker( m_loadingAlbumsMutex ); + m_loadingAlbums.remove( album ); + + if( !index.isValid() ) + return; + + int year = 0; + + if( !tracks.isEmpty() ) + { + Meta::YearPtr yearPtr = tracks.first()->year(); + if( yearPtr ) + year = yearPtr->year(); + + debug() << "Valid album year found:" << year; + } + + // Set the year in the thread associated with this + auto lambda = [=] () { + if( !m_years.contains( album.data() ) || m_years.value( album.data() ) != year ) + { + m_years[ album.data() ] = year; + emit dataChanged( index, index ); + } + }; + QTimer::singleShot( 0, this, lambda ); +} + void CollectionTreeItemModelBase::addQueryMaker( CollectionTreeItem* item, Collections::QueryMaker *qm ) const { connect( qm, &Collections::QueryMaker::newTracksReady, this, &CollectionTreeItemModelBase::newTracksReady, Qt::QueuedConnection ); connect( qm, &Collections::QueryMaker::newArtistsReady, this, &CollectionTreeItemModelBase::newArtistsReady, Qt::QueuedConnection ); connect( qm, &Collections::QueryMaker::newAlbumsReady, this, &CollectionTreeItemModelBase::newAlbumsReady, Qt::QueuedConnection ); connect( qm, &Collections::QueryMaker::newGenresReady, this, &CollectionTreeItemModelBase::newGenresReady, Qt::QueuedConnection ); connect( qm, &Collections::QueryMaker::newComposersReady, this, &CollectionTreeItemModelBase::newComposersReady, Qt::QueuedConnection ); connect( qm, &Collections::QueryMaker::newYearsReady, this, &CollectionTreeItemModelBase::newYearsReady, Qt::QueuedConnection ); connect( qm, &Collections::QueryMaker::newLabelsReady, this, &CollectionTreeItemModelBase::newLabelsReady, Qt::QueuedConnection ); connect( qm, &Collections::QueryMaker::newDataReady, this, &CollectionTreeItemModelBase::newDataReady, Qt::QueuedConnection ); connect( qm, &Collections::QueryMaker::queryDone, this, &CollectionTreeItemModelBase::queryDone, Qt::QueuedConnection ); m_runningQueries.insert( item, qm ); } void CollectionTreeItemModelBase::queryDone() { Collections::QueryMaker *qm = qobject_cast( sender() ); if( !qm ) return; CollectionTreeItem* item = 0; if( m_childQueries.contains( qm ) ) item = m_childQueries.take( qm ); else if( m_compilationQueries.contains( qm ) ) item = m_compilationQueries.take( qm ); else if( m_noLabelsQueries.contains( qm ) ) item = m_noLabelsQueries.take( qm ); if( item ) m_runningQueries.remove( item, qm ); //reset icon for this item if( item && item != m_rootItem ) { emit dataChanged( itemIndex( item ), itemIndex( item ) ); } //stop timer if there are no more animations active if( m_runningQueries.isEmpty() ) { emit allQueriesFinished( m_autoExpand ); m_autoExpand = false; // reset to default value m_timeLine->stop(); } qm->deleteLater(); } // TODO /** Small helper function to convert a list of e.g. tracks to a list of DataPtr */ -template +template static Meta::DataList -convertToDataList( const ListType& list ) +convertToDataList( const QList& list ) { Meta::DataList data; - foreach( PointerType p, list ) + for( const auto &p : list ) data << Meta::DataPtr::staticCast( p ); return data; } void CollectionTreeItemModelBase::newTracksReady( Meta::TrackList res ) { - newDataReady( convertToDataList( res ) ); + newDataReady( convertToDataList( res ) ); } void CollectionTreeItemModelBase::newArtistsReady( Meta::ArtistList res ) { - newDataReady( convertToDataList( res ) ); + newDataReady( convertToDataList( res ) ); } void CollectionTreeItemModelBase::newAlbumsReady( Meta::AlbumList res ) { - newDataReady( convertToDataList( res ) ); + newDataReady( convertToDataList( res ) ); } void CollectionTreeItemModelBase::newGenresReady( Meta::GenreList res ) { - newDataReady( convertToDataList( res ) ); + newDataReady( convertToDataList( res ) ); } void CollectionTreeItemModelBase::newComposersReady( Meta::ComposerList res ) { - newDataReady( convertToDataList( res ) ); + newDataReady( convertToDataList( res ) ); } void CollectionTreeItemModelBase::newYearsReady( Meta::YearList res ) { - newDataReady( convertToDataList( res ) ); + newDataReady( convertToDataList( res ) ); } void CollectionTreeItemModelBase::newLabelsReady( Meta::LabelList res ) { - newDataReady( convertToDataList( res ) ); + newDataReady( convertToDataList( res ) ); } void CollectionTreeItemModelBase::newDataReady( Meta::DataList data ) { //if we are expanding an item, we'll find the sender in childQueries //otherwise we are filtering all collections Collections::QueryMaker *qm = qobject_cast( sender() ); if( !qm ) return; if( m_childQueries.contains( qm ) ) handleNormalQueryResult( qm, data ); else if( m_compilationQueries.contains( qm ) ) handleSpecialQueryResult( CollectionTreeItem::VariousArtist, qm, data ); else if( m_noLabelsQueries.contains( qm ) ) handleSpecialQueryResult( CollectionTreeItem::NoLabel, qm, data ); } void CollectionTreeItemModelBase::handleSpecialQueryResult( CollectionTreeItem::Type type, Collections::QueryMaker *qm, const Meta::DataList &dataList ) { CollectionTreeItem *parent = 0; if( type == CollectionTreeItem::VariousArtist ) parent = m_compilationQueries.value( qm ); else if( type == CollectionTreeItem::NoLabel ) parent = m_noLabelsQueries.value( qm ); if( parent ) { QModelIndex parentIndex = itemIndex( parent ); //if the special query did not return a result we have to remove the //the special node itself if( dataList.isEmpty() ) { for( int i = 0; i < parent->childCount(); i++ ) { CollectionTreeItem *cti = parent->child( i ); if( cti->type() == type ) { //found the special node beginRemoveRows( parentIndex, cti->row(), cti->row() ); cti = 0; //will be deleted; parent->removeChild( i ); endRemoveRows(); break; } } //we have removed the special node if it existed return; } CollectionTreeItem *specialNode = 0; if( parent->childCount() == 0 ) { //we only insert the special node beginInsertRows( parentIndex, 0, 0 ); specialNode = new CollectionTreeItem( type, dataList, parent, this ); //set requiresUpdate, otherwise we will query for the children of specialNode again! specialNode->setRequiresUpdate( false ); endInsertRows(); } else { for( int i = 0; i < parent->childCount(); i++ ) { CollectionTreeItem *cti = parent->child( i ); if( cti->type() == type ) { //found the special node specialNode = cti; break; } } if( !specialNode ) { //we only insert the special node beginInsertRows( parentIndex, 0, 0 ); specialNode = new CollectionTreeItem( type, dataList, parent, this ); //set requiresUpdate, otherwise we will query for the children of specialNode again! specialNode->setRequiresUpdate( false ); endInsertRows(); } else { //only call populateChildren for the special node if we have not //created it in this method call. The special node ctor takes care //of that itself populateChildren( dataList, specialNode, itemIndex( specialNode ) ); } //populate children will call setRequiresUpdate on vaNode //but as the special query is based on specialNode's parent, //we have to call setRequiresUpdate on the parent too //yes, this will mean we will call setRequiresUpdate twice parent->setRequiresUpdate( false ); for( int count = specialNode->childCount(), i = 0; i < count; ++i ) { CollectionTreeItem *item = specialNode->child( i ); if ( m_expandedItems.contains( item->data() ) ) //item will always be a data item { listForLevel( item->level() + levelModifier(), item->queryMaker(), item ); } } } //if the special node exists, check if it has to be expanded if( specialNode ) { if( m_expandedSpecialNodes.contains( parent->parentCollection() ) ) { emit expandIndex( createIndex( 0, 0, specialNode ) ); //we have just inserted the vaItem at row 0 } } } } void CollectionTreeItemModelBase::handleNormalQueryResult( Collections::QueryMaker *qm, const Meta::DataList &dataList ) { CollectionTreeItem *parent = m_childQueries.value( qm ); if( parent ) { QModelIndex parentIndex = itemIndex( parent ); populateChildren( dataList, parent, parentIndex ); if ( parent->isDataItem() ) { if ( m_expandedItems.contains( parent->data() ) ) emit expandIndex( parentIndex ); else //simply insert the item, nothing will change if it is already in the set m_expandedItems.insert( parent->data() ); } } } void CollectionTreeItemModelBase::populateChildren( const DataList &dataList, CollectionTreeItem *parent, const QModelIndex &parentIndex ) { CategoryId::CatMenuId childCategory = levelCategory( parent->level() ); // add new rows after existing ones here (which means all artists nodes // will be inserted after the "Various Artists" node) // figure out which children of parent have to be removed, // which new children have to be added, and preemptively emit dataChanged for the rest // have to check how that influences performance... const QSet dataSet = dataList.toSet(); QSet childrenSet; foreach( CollectionTreeItem *child, parent->children() ) { // we don't add null children, these are special-cased below if( !child->data() ) continue; childrenSet.insert( child->data() ); } const QSet dataToBeAdded = dataSet - childrenSet; const QSet dataToBeRemoved = childrenSet - dataSet; // first remove all rows that have to be removed // walking through the cildren in reverse order does not screw up the order for( int i = parent->childCount() - 1; i >= 0; i-- ) { CollectionTreeItem *child = parent->child( i ); // should this child be removed? bool toBeRemoved; if( child->isDataItem() ) toBeRemoved = dataToBeRemoved.contains( child->data() ); else if( child->isVariousArtistItem() ) toBeRemoved = !variousArtistCategories.contains( childCategory ); else if( child->isNoLabelItem() ) toBeRemoved = childCategory != CategoryId::Label; else { warning() << "Unknown child type encountered in populateChildren(), removing"; toBeRemoved = true; } if( toBeRemoved ) { beginRemoveRows( parentIndex, i, i ); parent->removeChild( i ); endRemoveRows(); } else { // the remainging child items may be dirty, so refresh them if( child->isDataItem() && child->data() && m_expandedItems.contains( child->data() ) ) ensureChildrenLoaded( child ); // tell the view that the existing children may have changed QModelIndex idx = index( i, 0, parentIndex ); emit dataChanged( idx, idx ); } } // add the new rows if( !dataToBeAdded.isEmpty() ) { int lastRow = parent->childCount() - 1; //the above check ensures that Qt does not crash on beginInsertRows ( because lastRow+1 > lastRow+0) beginInsertRows( parentIndex, lastRow + 1, lastRow + dataToBeAdded.count() ); foreach( Meta::DataPtr data, dataToBeAdded ) { new CollectionTreeItem( data, parent, this ); } endInsertRows(); } parent->setRequiresUpdate( false ); } void CollectionTreeItemModelBase::updateHeaderText() { m_headerText.clear(); for( int i=0; i< m_levelType.count(); ++i ) m_headerText += nameForLevel( i ) + " / "; m_headerText.chop( 3 ); } QIcon CollectionTreeItemModelBase::iconForCategory( CategoryId::CatMenuId category ) { switch( category ) { case CategoryId::Album : return QIcon::fromTheme( "media-optical-amarok" ); case CategoryId::Artist : return QIcon::fromTheme( "view-media-artist-amarok" ); case CategoryId::AlbumArtist : return QIcon::fromTheme( "view-media-artist-amarok" ); case CategoryId::Composer : return QIcon::fromTheme( "filename-composer-amarok" ); case CategoryId::Genre : return QIcon::fromTheme( "favorite-genres-amarok" ); case CategoryId::Year : return QIcon::fromTheme( "clock" ); case CategoryId::Label : return QIcon::fromTheme( "label-amarok" ); case CategoryId::None: default: return QIcon::fromTheme( "image-missing" ); } } QIcon CollectionTreeItemModelBase::iconForLevel( int level ) const { return iconForCategory( m_levelType.value( level ) ); } QString CollectionTreeItemModelBase::nameForCategory( CategoryId::CatMenuId category, bool showYears ) { switch( category ) { case CategoryId::Album: return showYears ? i18n( "Year - Album" ) : i18n( "Album" ); case CategoryId::Artist: return i18n( "Track Artist" ); case CategoryId::AlbumArtist: return i18n( "Album Artist" ); case CategoryId::Composer: return i18n( "Composer" ); case CategoryId::Genre: return i18n( "Genre" ); case CategoryId::Year: return i18n( "Year" ); case CategoryId::Label: return i18n( "Label" ); case CategoryId::None: return i18n( "None" ); default: return QString(); } } QString CollectionTreeItemModelBase::nameForLevel( int level ) const { return nameForCategory( m_levelType.value( level ), AmarokConfig::showYears() ); } void CollectionTreeItemModelBase::handleCompilations( Collections::QueryMaker::QueryType queryType, CollectionTreeItem *parent ) const { // this method will be called when we retrieve a list of artists from the database. // we have to query for all compilations, and then add a "Various Artists" node if at least // one compilation exists Collections::QueryMaker *qm = parent->queryMaker(); qm->setQueryType( queryType ); qm->setAlbumQueryMode( Collections::QueryMaker::OnlyCompilations ); for( CollectionTreeItem *tmp = parent; tmp; tmp = tmp->parent() ) tmp->addMatch( qm, levelCategory( tmp->level() - 1 ) ); Collections::addTextualFilter( qm, m_currentFilter ); addQueryMaker( parent, qm ); m_compilationQueries.insert( qm, parent ); qm->run(); } void CollectionTreeItemModelBase::handleTracksWithoutLabels( Collections::QueryMaker::QueryType queryType, CollectionTreeItem *parent ) const { Collections::QueryMaker *qm = parent->queryMaker(); qm->setQueryType( queryType ); qm->setLabelQueryMode( Collections::QueryMaker::OnlyWithoutLabels ); for( CollectionTreeItem *tmp = parent; tmp; tmp = tmp->parent() ) tmp->addMatch( qm, levelCategory( tmp->level() - 1 ) ); Collections::addTextualFilter( qm, m_currentFilter ); addQueryMaker( parent, qm ); m_noLabelsQueries.insert( qm, parent ); qm->run(); } void CollectionTreeItemModelBase::startAnimationTick() { //start animation if( ( m_timeLine->state() != QTimeLine::Running ) && !m_runningQueries.isEmpty() ) m_timeLine->start(); } void CollectionTreeItemModelBase::loadingAnimationTick() { if ( m_animFrame == 0 ) m_currentAnimPixmap = m_loading2; else m_currentAnimPixmap = m_loading1; m_animFrame = 1 - m_animFrame; //trigger an update of all items being populated at the moment; QList< CollectionTreeItem * > items = m_runningQueries.uniqueKeys(); foreach ( CollectionTreeItem* item, items ) { if( item == m_rootItem ) continue; emit dataChanged( itemIndex( item ), itemIndex( item ) ); } } QString CollectionTreeItemModelBase::currentFilter() const { return m_currentFilter; } void CollectionTreeItemModelBase::setCurrentFilter( const QString &filter ) { m_currentFilter = filter; slotFilter( /* autoExpand */ true ); } void CollectionTreeItemModelBase::slotFilter( bool autoExpand ) { m_autoExpand = autoExpand; filterChildren(); // following is not auto-expansion, it is restoring the state before filtering foreach( Collections::Collection *expanded, m_expandedCollections ) { CollectionTreeItem *expandedItem = m_collections.value( expanded->collectionId() ).second; if( expandedItem ) emit expandIndex( itemIndex( expandedItem ) ); } } void CollectionTreeItemModelBase::slotCollapsed( const QModelIndex &index ) { if ( index.isValid() ) //probably unnecessary, but let's be safe { CollectionTreeItem *item = static_cast( index.internalPointer() ); switch( item->type() ) { case CollectionTreeItem::Root: break; // nothing to do case CollectionTreeItem::Collection: m_expandedCollections.remove( item->parentCollection() ); break; case CollectionTreeItem::VariousArtist: case CollectionTreeItem::NoLabel: m_expandedSpecialNodes.remove( item->parentCollection() ); break; case CollectionTreeItem::Data: m_expandedItems.remove( item->data() ); break; } } } void CollectionTreeItemModelBase::slotExpanded( const QModelIndex &index ) { if( !index.isValid() ) return; CollectionTreeItem *item = static_cast( index.internalPointer() ); // we are really only interested in the special nodes here. // we have to remember whether the user expanded a various artists/no labels node or not. // otherwise we won't be able to automatically expand the special node after filtering again // there is exactly one special node per type per collection, so use the collection to store that information // we also need to store collection expansion state here as they are no longer // added to th expanded set in handleNormalQueryResult() switch( item->type() ) { case CollectionTreeItem::VariousArtist: case CollectionTreeItem::NoLabel: m_expandedSpecialNodes.insert( item->parentCollection() ); break; case CollectionTreeItem::Collection: m_expandedCollections.insert( item->parentCollection() ); default: break; } } void CollectionTreeItemModelBase::markSubTreeAsDirty( CollectionTreeItem *item ) { //tracks are the leaves so they are never dirty if( !item->isTrackItem() ) item->setRequiresUpdate( true ); for( int i = 0; i < item->childCount(); i++ ) { markSubTreeAsDirty( item->child( i ) ); } } void CollectionTreeItemModelBase::itemAboutToBeDeleted( CollectionTreeItem *item ) { // also all the children will be deleted foreach( CollectionTreeItem *child, item->children() ) itemAboutToBeDeleted( child ); if( !m_runningQueries.contains( item ) ) return; // TODO: replace this hack with QWeakPointer now than we depend on Qt >= 4.8 foreach(Collections::QueryMaker *qm, m_runningQueries.values( item )) { m_childQueries.remove( qm ); m_compilationQueries.remove( qm ); m_noLabelsQueries.remove( qm ); m_runningQueries.remove(item, qm); //Disconnect all signals from the QueryMaker so we do not get notified about the results qm->disconnect(); qm->abortQuery(); //Nuke it qm->deleteLater(); } } void CollectionTreeItemModelBase::setDragSourceCollections( const QSet &collections ) { m_dragSourceCollections = collections; } bool CollectionTreeItemModelBase::hasRunningQueries() const { return !m_runningQueries.isEmpty(); } CategoryId::CatMenuId CollectionTreeItemModelBase::levelCategory( const int level ) const { const int actualLevel = level + levelModifier(); if( actualLevel >= 0 && actualLevel < m_levelType.count() ) return m_levelType.at( actualLevel ); return CategoryId::None; } diff --git a/src/browsers/CollectionTreeItemModelBase.h b/src/browsers/CollectionTreeItemModelBase.h index f3cf1e55d5..9aea1864a4 100644 --- a/src/browsers/CollectionTreeItemModelBase.h +++ b/src/browsers/CollectionTreeItemModelBase.h @@ -1,202 +1,215 @@ /**************************************************************************************** * Copyright (c) 2007 Alexandre Pereira de Oliveira * * Copyright (c) 2007-2009 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 . * ****************************************************************************************/ #ifndef COLLECTIONTREEITEMMODELBASE_H #define COLLECTIONTREEITEMMODELBASE_H #include "amarok_export.h" #include "core/collections/QueryMaker.h" #include "core/meta/forward_declarations.h" #include "CollectionTreeItem.h" #include #include #include #include #include #include namespace Collections { class Collection; } class CollectionTreeItem; +class QMutex; class QTimeLine; +class TrackLoaderJob; typedef QPair CollectionRoot; /** @author Nikolaj Hald Nielsen */ class AMAROK_EXPORT CollectionTreeItemModelBase : public QAbstractItemModel { Q_OBJECT + friend class TrackLoaderJob; + public: CollectionTreeItemModelBase(); virtual ~CollectionTreeItemModelBase(); virtual Qt::ItemFlags flags(const QModelIndex &index) const; virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; virtual QModelIndex parent(const QModelIndex &index) const; virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; virtual int columnCount(const QModelIndex &parent = QModelIndex()) const; virtual bool hasChildren ( const QModelIndex & parent = QModelIndex() ) const; // Writable.. virtual bool setData( const QModelIndex &index, const QVariant &value, int role = Qt::EditRole ); virtual QStringList mimeTypes() const; virtual QMimeData* mimeData( const QModelIndexList &indices ) const; virtual QMimeData* mimeData( const QList &items ) const; virtual void listForLevel( int level, Collections::QueryMaker *qm, CollectionTreeItem* parent ); virtual void setLevels( const QList &levelType ); virtual QList levels() const { return m_levelType; } virtual CategoryId::CatMenuId levelCategory( const int level ) const; QString currentFilter() const; void setCurrentFilter( const QString &filter ); void itemAboutToBeDeleted( CollectionTreeItem *item ); /** * This should be called every time a drag enters collection browser */ void setDragSourceCollections( const QSet &collections ); /** * Return true if there are any queries still running. If this returns true, * you can expect allQueriesFinished(bool) signal in some time. */ bool hasRunningQueries() const; static QIcon iconForCategory( CategoryId::CatMenuId category ); static QString nameForCategory( CategoryId::CatMenuId category, bool showYears = false ); void ensureChildrenLoaded( CollectionTreeItem *item ); /** * Get a pointer to colleciton tree item given its index. It is not safe to * cache this pointer unless QPointer is used. */ CollectionTreeItem *treeItem( const QModelIndex &index ) const; /** * Get (create) index for a collection tree item. The caller must ensure this * item is in this model. Invalid model index is returned on null or root item. */ QModelIndex itemIndex( CollectionTreeItem *item ) const; Q_SIGNALS: void expandIndex( const QModelIndex &index ); void allQueriesFinished( bool autoExpand ); public Q_SLOTS: virtual void queryDone(); void newTracksReady( Meta::TrackList ); void newArtistsReady( Meta::ArtistList ); void newAlbumsReady( Meta::AlbumList ); void newGenresReady( Meta::GenreList ); void newComposersReady( Meta::ComposerList ); void newYearsReady( Meta::YearList ); void newLabelsReady( Meta::LabelList ); virtual void newDataReady( Meta::DataList data ); /** * Apply the current filter. * * @param autoExpand whether to trigger automatic expansion of the tree after * filtering is done. This should be set to true only if filter is run after * user has actually just typed something and defaults to false. */ void slotFilter( bool autoExpand = false ); void slotFilterWithoutAutoExpand() { slotFilter( false ); } void slotCollapsed( const QModelIndex &index ); void slotExpanded( const QModelIndex &index ); private: void handleSpecialQueryResult( CollectionTreeItem::Type type, Collections::QueryMaker *qm, const Meta::DataList &dataList ); void handleNormalQueryResult( Collections::QueryMaker *qm, const Meta::DataList &dataList ); Collections::QueryMaker::QueryType mapCategoryToQueryType( int levelType ) const; + /** + * This function is thread-safe + */ + void tracksLoaded( Meta::AlbumPtr album, const QModelIndex &index, const Meta::TrackList &tracks ); + + QMutex *m_loadingAlbumsMutex; + QHash m_years; + mutable QSet m_loadingAlbums; + protected: /** Adds the query maker to the running queries and connects the slots */ void addQueryMaker( CollectionTreeItem* item, Collections::QueryMaker *qm ) const; virtual void populateChildren(const Meta::DataList &dataList, CollectionTreeItem *parent, const QModelIndex &parentIndex ); virtual void updateHeaderText(); virtual QIcon iconForLevel( int level ) const; virtual QString nameForLevel( int level ) const; virtual int levelModifier() const = 0; virtual QVariant dataForItem( CollectionTreeItem *item, int role, int level = -1 ) const; virtual void filterChildren() = 0; void markSubTreeAsDirty( CollectionTreeItem *item ); /** Initiates a special search for albums without artists */ void handleCompilations( Collections::QueryMaker::QueryType queryType, CollectionTreeItem *parent ) const; /** Initiates a special search for tracks without label */ void handleTracksWithoutLabels( Collections::QueryMaker::QueryType queryType, CollectionTreeItem *parent ) const; QString m_headerText; CollectionTreeItem *m_rootItem; QList m_levelType; QTimeLine *m_timeLine; int m_animFrame; QPixmap m_loading1, m_loading2, m_currentAnimPixmap; //icons for loading animation QString m_currentFilter; QSet m_expandedItems; QSet m_expandedCollections; QSet m_expandedSpecialNodes; /** * Contents of this set are undefined if there is no active drag 'n drop operation. * Additionally, you may _never_ dereference pointers in this set, just compare * them with other pointers */ QSet m_dragSourceCollections; QHash m_collections; //I'll concide this one... :-) mutable QHash m_childQueries; mutable QHash m_compilationQueries; mutable QHash m_noLabelsQueries; mutable QMultiHash m_runningQueries; bool m_autoExpand; // whether to expand tree after queries are done protected Q_SLOTS: void startAnimationTick(); void loadingAnimationTick(); }; #endif diff --git a/src/configdialog/ConfigDialog.h b/src/configdialog/ConfigDialog.h index f51c72639f..039ab04bb9 100644 --- a/src/configdialog/ConfigDialog.h +++ b/src/configdialog/ConfigDialog.h @@ -1,71 +1,71 @@ /**************************************************************************************** * Copyright (c) 2004-2008 Mark Kretschmann * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #ifndef AMAROK2CONFIGDIALOG_H #define AMAROK2CONFIGDIALOG_H #include #include #include class ConfigDialogBase; class ConfigDialogBase; class Amarok2ConfigDialog : public KConfigDialog { Q_OBJECT public: Amarok2ConfigDialog( QWidget *parent, const char* name, KConfigSkeleton *config ); ~Amarok2ConfigDialog(); void addPage( ConfigDialogBase *page, const QString &itemName, const QString &pixmapName, const QString &header = QString(), bool manage = true ); public Q_SLOTS: /** * Shows the config dialog and sets the current page to "page" * * @param page (class name of the dialog). */ void show( QString page ); /** * Updates the state of the Apply button. Useful for widgets that are not managed by KConfigXT. */ void updateButtons(); protected Q_SLOTS: - void updateSettings() Q_DECL_OVERRIDE; - void updateWidgets() Q_DECL_OVERRIDE; - void updateWidgetsDefault() Q_DECL_OVERRIDE; + void updateSettings() override; + void updateWidgets() override; + void updateWidgetsDefault() override; protected: - bool hasChanged() Q_DECL_OVERRIDE; - bool isDefault() Q_DECL_OVERRIDE; + bool hasChanged() override; + bool isDefault() override; private: QList m_pageList; QMap m_pageMap; static QString s_currentPage; }; #endif // AMAROK2CONFIGDIALOG_H diff --git a/src/configdialog/dialogs/CollectionConfig.h b/src/configdialog/dialogs/CollectionConfig.h index ae3a2d737a..c6fa3d224f 100644 --- a/src/configdialog/dialogs/CollectionConfig.h +++ b/src/configdialog/dialogs/CollectionConfig.h @@ -1,44 +1,44 @@ /**************************************************************************************** * Copyright (c) 2004-2007 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 COLLECTIONCONFIG_H #define COLLECTIONCONFIG_H #include "configdialog/ConfigDialogBase.h" class CollectionSetup; class CollectionConfig : public ConfigDialogBase { Q_OBJECT public: explicit CollectionConfig( Amarok2ConfigDialog* parent ); virtual ~CollectionConfig(); - bool hasChanged() Q_DECL_OVERRIDE; - bool isDefault() Q_DECL_OVERRIDE; - void updateSettings() Q_DECL_OVERRIDE; + bool hasChanged() override; + bool isDefault() override; + void updateSettings() override; private: CollectionSetup* m_collectionSetup; }; #endif diff --git a/src/configdialog/dialogs/DatabaseConfig.h b/src/configdialog/dialogs/DatabaseConfig.h index 0dfc748034..2671c882ec 100644 --- a/src/configdialog/dialogs/DatabaseConfig.h +++ b/src/configdialog/dialogs/DatabaseConfig.h @@ -1,60 +1,60 @@ /**************************************************************************************** * 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 . * ****************************************************************************************/ #ifndef DATABASECONFIG_H #define DATABASECONFIG_H #include "ui_DatabaseConfig.h" #include "configdialog/ConfigDialogBase.h" class KConfigDialogManager; class KConfigSkeleton; class DatabaseConfig : public ConfigDialogBase, public Ui_DatabaseConfig { Q_OBJECT public: DatabaseConfig( Amarok2ConfigDialog* parent, KConfigSkeleton *config ); virtual ~DatabaseConfig(); - virtual bool hasChanged() Q_DECL_OVERRIDE; - virtual bool isDefault() Q_DECL_OVERRIDE; - virtual void updateSettings() Q_DECL_OVERRIDE; + virtual bool hasChanged() override; + virtual bool isDefault() override; + virtual void updateSettings() override; public Q_SLOTS: void toggleExternalConfigAvailable( int checkBoxState ); void testDatabaseConnection(); private Q_SLOTS: void updateSQLQuery(); private: /** Returns true if the configuration is complete. * * Complete menas that the database, user and host are filled out. */ bool isSQLInfoPresent() const; KConfigDialogManager* m_configManager; }; #endif diff --git a/src/configdialog/dialogs/GeneralConfig.h b/src/configdialog/dialogs/GeneralConfig.h index 9ad42bff0f..f6e5a38e79 100644 --- a/src/configdialog/dialogs/GeneralConfig.h +++ b/src/configdialog/dialogs/GeneralConfig.h @@ -1,36 +1,36 @@ /**************************************************************************************** * Copyright (c) 2004-2007 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 GENERALCONFIG_H #define GENERALCONFIG_H #include "ui_GeneralConfig.h" #include "configdialog/ConfigDialogBase.h" class GeneralConfig : public ConfigDialogBase, public Ui_GeneralConfig { Q_OBJECT public: explicit GeneralConfig( Amarok2ConfigDialog* parent ); virtual ~GeneralConfig(); - bool hasChanged() Q_DECL_OVERRIDE; - bool isDefault() Q_DECL_OVERRIDE; - void updateSettings() Q_DECL_OVERRIDE; + bool hasChanged() override; + bool isDefault() override; + void updateSettings() override; }; #endif diff --git a/src/configdialog/dialogs/MetadataConfig.h b/src/configdialog/dialogs/MetadataConfig.h index 6ccd1c4e34..c2c56054f9 100644 --- a/src/configdialog/dialogs/MetadataConfig.h +++ b/src/configdialog/dialogs/MetadataConfig.h @@ -1,59 +1,59 @@ /**************************************************************************************** * 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 METADATACONFIG_H #define METADATACONFIG_H #include "ui_MetadataConfig.h" #include "configdialog/ConfigDialogBase.h" namespace StatSyncing { class Config; } class MetadataConfig : public ConfigDialogBase, private Ui_MetadataConfig { Q_OBJECT public: explicit MetadataConfig( Amarok2ConfigDialog *parent ); virtual ~MetadataConfig(); - bool isDefault() Q_DECL_OVERRIDE; - bool hasChanged() Q_DECL_OVERRIDE; - void updateSettings() Q_DECL_OVERRIDE; + bool isDefault() override; + bool hasChanged() override; + void updateSettings() override; Q_SIGNALS: void changed(); private Q_SLOTS: void slotForgetCollections(); void slotUpdateForgetButton(); void slotUpdateConfigureExcludedLabelsLabel(); void slotConfigureExcludedLabels(); void slotConfigureProvider(); void slotUpdateProviderConfigureButton(); void slotCreateProviderDialog(); private: int writeBackCoverDimensions() const; qint64 checkedFields() const; QPointer m_statSyncingConfig; }; #endif // METADATACONFIG_H diff --git a/src/configdialog/dialogs/NotificationsConfig.h b/src/configdialog/dialogs/NotificationsConfig.h index dbdc741b10..2aaed2db69 100644 --- a/src/configdialog/dialogs/NotificationsConfig.h +++ b/src/configdialog/dialogs/NotificationsConfig.h @@ -1,57 +1,57 @@ /**************************************************************************************** * Copyright (c) 2004-2007 Mark Kretschmann * * Copyright (c) 2004 Frederik Holljen * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General 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 NOTIFICATIONSCONFIG_H #define NOTIFICATIONSCONFIG_H #include "ui_NotificationsConfig.h" #include "configdialog/ConfigDialogBase.h" #include "widgets/Osd.h" class OSDPreviewWidget; class NotificationsConfig : public ConfigDialogBase, public Ui_NotificationsConfig { Q_OBJECT public: explicit NotificationsConfig( Amarok2ConfigDialog* parent ); virtual ~NotificationsConfig(); - bool hasChanged() Q_DECL_OVERRIDE; - bool isDefault() Q_DECL_OVERRIDE; - void updateSettings() Q_DECL_OVERRIDE; + bool hasChanged() override; + bool isDefault() override; + void updateSettings() override; Q_SIGNALS: void changed(); private Q_SLOTS: void slotPositionChanged(); void useCustomColorsToggled( bool ); void setGrowlEnabled( bool ); private: OSDPreviewWidget* m_osdPreview; OSDWidget::Alignment m_oldAlignment; uint m_oldYOffset; - void hideEvent( QHideEvent* ); - void showEvent( QShowEvent* ); + void hideEvent( QHideEvent* ) override; + void showEvent( QShowEvent* ) override; }; #endif diff --git a/src/configdialog/dialogs/PlaybackConfig.h b/src/configdialog/dialogs/PlaybackConfig.h index ddaa662625..0f0d4a73e9 100644 --- a/src/configdialog/dialogs/PlaybackConfig.h +++ b/src/configdialog/dialogs/PlaybackConfig.h @@ -1,41 +1,41 @@ /**************************************************************************************** * Copyright (c) 2004-2009 Mark Kretschmann * * Copyright (c) 2009 Artur Szymiec * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #ifndef PLAYBACKCONFIG_H #define PLAYBACKCONFIG_H #include "ui_PlaybackConfig.h" #include "configdialog/ConfigDialogBase.h" class PlaybackConfig : public ConfigDialogBase, public Ui_PlaybackConfig { Q_OBJECT public: explicit PlaybackConfig( Amarok2ConfigDialog* parent ); virtual ~PlaybackConfig(); - bool hasChanged() Q_DECL_OVERRIDE; - bool isDefault() Q_DECL_OVERRIDE; - void updateSettings() Q_DECL_OVERRIDE; + bool hasChanged() override; + bool isDefault() override; + void updateSettings() override; private Q_SLOTS: void configurePhonon(); void setFadeoutState(); }; #endif diff --git a/src/configdialog/dialogs/PluginsConfig.h b/src/configdialog/dialogs/PluginsConfig.h index 0085a51fb5..28dc53fb44 100644 --- a/src/configdialog/dialogs/PluginsConfig.h +++ b/src/configdialog/dialogs/PluginsConfig.h @@ -1,47 +1,47 @@ /**************************************************************************************** * 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_PLUGINSCONFIG_H #define AMAROK_PLUGINSCONFIG_H #include "configdialog/ConfigDialogBase.h" class KPluginSelector; /** * A widget that allows configuration of plugins */ class PluginsConfig : public ConfigDialogBase { Q_OBJECT public: explicit PluginsConfig( Amarok2ConfigDialog *parent ); virtual ~PluginsConfig(); - void updateSettings() Q_DECL_OVERRIDE; - bool hasChanged() Q_DECL_OVERRIDE; - bool isDefault() Q_DECL_OVERRIDE; + void updateSettings() override; + bool hasChanged() override; + bool isDefault() override; public Q_SLOTS: void slotConfigChanged( bool changed ); private: bool m_configChanged; KPluginSelector *m_selector; }; #endif // AMAROK_PLUGINSCONFIG_H diff --git a/src/configdialog/dialogs/ScriptsConfig.h b/src/configdialog/dialogs/ScriptsConfig.h index aefa7249f8..994893d639 100644 --- a/src/configdialog/dialogs/ScriptsConfig.h +++ b/src/configdialog/dialogs/ScriptsConfig.h @@ -1,70 +1,70 @@ /**************************************************************************************** * Copyright (c) 2010 Rick W. Chen * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #ifndef AMAROK_SCRIPTSCONFIG_H #define AMAROK_SCRIPTSCONFIG_H #include "configdialog/ConfigDialogBase.h" #include class KArchiveDirectory; class KArchiveFile; class ScriptSelector; class QPushButton; class QVBoxLayout; /** * A widget that allows configuration of scripts */ class ScriptsConfig : public ConfigDialogBase { Q_OBJECT public: explicit ScriptsConfig( Amarok2ConfigDialog *parent ); virtual ~ScriptsConfig(); - void updateSettings() Q_DECL_OVERRIDE; - bool hasChanged() Q_DECL_OVERRIDE; - bool isDefault() Q_DECL_OVERRIDE; + void updateSettings() override; + bool hasChanged() override; + bool isDefault() override; public Q_SLOTS: void slotConfigChanged( bool changed ); private Q_SLOTS: void slotManageScripts(); void installLocalScript(); void slotReloadScriptSelector(); void slotUpdateScripts(); void slotUninstallScript(); void restoreScrollBar(); private: const KArchiveFile *findSpecFile( const KArchiveDirectory *dir ) const; void removeDir( const QString &dirPath ) const; bool m_configChanged; ScriptSelector *m_selector; QTimer *m_timer; QVBoxLayout *m_verticalLayout; QPushButton *m_uninstallButton; QObject *m_parent; ScriptSelector *m_oldSelector; }; #endif // AMAROK_SCRIPTSCONFIG_H diff --git a/src/context/AmarokContextPackageStructure.h b/src/context/AmarokContextPackageStructure.h index 339e2081d4..852b68d84a 100644 --- a/src/context/AmarokContextPackageStructure.h +++ b/src/context/AmarokContextPackageStructure.h @@ -1,33 +1,33 @@ /* * Copyright (C) 2017 Malte Veerman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 AMAROKCONTEXTPACKAGESTRUCTURE_H #define AMAROKCONTEXTPACKAGESTRUCTURE_H #include class AmarokContextPackageStructure : public KPackage::PackageStructure { Q_OBJECT public: - virtual void initPackage(KPackage::Package *package) Q_DECL_OVERRIDE; + virtual void initPackage(KPackage::Package *package) override; }; #endif // AMAROKCONTEXTPACKAGESTRUCTURE_H diff --git a/src/context/AppletModel.h b/src/context/AppletModel.h index b140dbc452..76de5aff0a 100644 --- a/src/context/AppletModel.h +++ b/src/context/AppletModel.h @@ -1,146 +1,146 @@ /**************************************************************************************** * Copyright (c) 2017 Malte Veerman * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General 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 APPLETMODEL_H #define APPLETMODEL_H #include #include class KPluginMetaData; namespace Context { class AppletLoader; class AppletPackage; class AppletModel : public QAbstractListModel { Q_OBJECT public: enum Role { Name, Id, Icon, Mainscript, Collapsed, PackagePath, ContentHeight }; Q_ENUM(Role) explicit AppletModel(AppletLoader *loader, QObject *parent = Q_NULLPTR); virtual ~AppletModel(); - virtual int rowCount(const QModelIndex& parent) const Q_DECL_OVERRIDE; - virtual QVariant data(const QModelIndex& index, int role) const Q_DECL_OVERRIDE; - virtual bool setData(const QModelIndex& index, const QVariant& value, int role) Q_DECL_OVERRIDE; - virtual QHash< int, QByteArray > roleNames() const Q_DECL_OVERRIDE; + virtual int rowCount(const QModelIndex& parent) const override; + virtual QVariant data(const QModelIndex& index, int role) const override; + virtual bool setData(const QModelIndex& index, const QVariant& value, int role) override; + virtual QHash< int, QByteArray > roleNames() const override; AppletLoader* loader() const { return m_loader; } Q_INVOKABLE void setAppletCollapsed(const QString &id, bool collapsed); Q_INVOKABLE void setAppletContentHeight(const QString& id, qreal height); Q_INVOKABLE QUrl imageUrl(const QString &id, const QString &imageName); public Q_SLOTS: void newApplets(const QList &applets); protected: AppletPackage findPackage(const QString &id); private: QList m_packages; AppletLoader *const m_loader; }; class AppletProxyModel : public QSortFilterProxyModel { Q_OBJECT Q_PROPERTY(QStringList enabledApplets READ enabledApplets NOTIFY enabledAppletsChanged) public: explicit AppletProxyModel(AppletModel *appletModel, QObject *parent = Q_NULLPTR); virtual ~AppletProxyModel(); /** * @returns QStringList with ids of all enabled applets. * Sorted in ascending order of place of applets. */ QStringList enabledApplets() const; /** * Set an applet to be enabled or disabled. Does nothing if id is invalid. * Disabling an applet sets its place to -1. * * @param id Id of the applet. * @param enabled Set to true if applet should be enabled and false for disabled. * @param place The place of the applet after enabling. -1 appends the applet to the end of the list. * Irrelevant if applet is to be disabled. */ Q_INVOKABLE void setAppletEnabled(const QString &id, bool enabled, int place = -1); /** * Set an applet's place. Does nothing if id is invalid. * Enables the applet if it is disabled. * * @param id Id of the applet. * @param place The new place of the applet. Negative values disable the applet. */ Q_INVOKABLE void setAppletPlace(const QString &id, int place); /** * Find out an applet's place. * * @returns an integer with the applet's place. -1 if the applet is disabled. * * @param id Id of applet's place to be returned. */ Q_INVOKABLE int appletPlace(const QString &id) const; /** * Find out if an applet is enabled or disabled. * * @returns true if applet is enabled. Returns false if not. * * @param id Id of the applet. */ Q_INVOKABLE bool appletEnabled(const QString &id) const; /** * Clear the context area by disabling all applets */ Q_INVOKABLE void clear(); Q_SIGNALS: void enabledAppletsChanged(); protected: - bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const Q_DECL_OVERRIDE; - bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const Q_DECL_OVERRIDE; + bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override; + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; private: AppletModel *m_appletModel; }; } #endif // APPLETMODEL_H diff --git a/src/context/ContextView.cpp b/src/context/ContextView.cpp index c91b4006b1..0e680ea27d 100644 --- a/src/context/ContextView.cpp +++ b/src/context/ContextView.cpp @@ -1,180 +1,178 @@ /**************************************************************************************** * Copyright (c) 2007-2008 Leo Franchi * * Copyright (c) 2008 William Viana Soares * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General 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 "ContextView" #include "ContextView.h" #include "AppletLoader.h" #include "AppletModel.h" #include "PaletteHandler.h" #include "SvgHandler.h" #include "amarokurls/AmarokUrlHandler.h" #include "amarokurls/ContextUrlRunner.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include "core/meta/Meta.h" #include #include #include #include #include #include #include #include #include #include #include namespace Context { ContextView* ContextView::s_self = Q_NULLPTR; ContextView::ContextView( QWidget *parent ) : QQuickWidget( parent ) , m_urlRunner( Q_NULLPTR ) , m_loader( new AppletLoader( this ) ) , m_appletModel( new AppletModel( m_loader, this ) ) , m_proxyModel( new AppletProxyModel( m_appletModel, this ) ) { DEBUG_BLOCK KDeclarative::KDeclarative decl; decl.setDeclarativeEngine( engine() ); decl.setupBindings(); connect( this, &QQuickWidget::statusChanged, this, &ContextView::slotStatusChanged ); connect( The::paletteHandler(), &PaletteHandler::newPalette, this, &ContextView::updatePalette ); m_urlRunner = new ContextUrlRunner(); The::amarokUrlHandler()->registerRunner( m_urlRunner, "context" ); rootContext()->setContextProperty( QStringLiteral( "AppletModel" ), m_appletModel ); rootContext()->setContextProperty( QStringLiteral( "AppletProxyModel" ), m_proxyModel ); rootContext()->setContextProperty( QStringLiteral( "Context" ), this ); rootContext()->setContextProperty( QStringLiteral( "Svg" ), The::svgHandler() ); quickWindow()->setColor( The::paletteHandler()->palette().color( QPalette::Window ) ); auto qmlPackage = KPackage::PackageLoader::self()->loadPackage( QStringLiteral( "KPackage/GenericQML" ), QStringLiteral( "org.kde.amarok.context" ) ); Q_ASSERT( qmlPackage.isValid() ); const QString sourcePath = qmlPackage.filePath( "mainscript" ); Q_ASSERT( QFile::exists( sourcePath ) ); ::debug() << "Loading context qml mainscript:" << sourcePath; setSource( QUrl::fromLocalFile( sourcePath ) ); setResizeMode( SizeRootObjectToView ); // keep this assignment at bottom so that premature usage of ::self() asserts out s_self = this; } ContextView::~ContextView() { DEBUG_BLOCK delete m_urlRunner; s_self = Q_NULLPTR; } QStringList ContextView::currentApplets() const { QStringList appletNames; auto applets = m_loader->enabledApplets(); for( const auto &applet : applets ) { appletNames << applet.pluginId(); } ::debug() << "Current applets: " << appletNames; return appletNames; } QStringList ContextView::currentAppletNames() const { QStringList appletNames; auto applets = m_loader->enabledApplets(); for( const auto &applet : applets ) { appletNames << applet.name(); } ::debug() << "Current applet names: " << appletNames; return appletNames; } void ContextView::runLink( const QUrl& link ) const { if( link.scheme() == QStringLiteral( "amarok" ) ) { AmarokUrl aUrl( link.toString() ); aUrl.run(); } else QDesktopServices::openUrl( link ); } void ContextView::slotStatusChanged( Status status ) { if( status == Error ) for( const auto &e : errors() ) error( e.description() ); } void ContextView::updatePalette( const QPalette &palette ) { quickWindow()->setColor( palette.color( QPalette::Window ) ); } void ContextView::debug( const QString &error ) const { ::debug() << error; } void ContextView::warning( const QString &error ) const { ::warning() << error; } void ContextView::error( const QString &error ) const { ::error() << error; } } // Context namespace - -#include diff --git a/src/context/LyricsManager.cpp b/src/context/LyricsManager.cpp deleted file mode 100644 index c27698b195..0000000000 --- a/src/context/LyricsManager.cpp +++ /dev/null @@ -1,271 +0,0 @@ -/**************************************************************************************** - * Copyright (c) 2007 Leo Franchi * - * Copyright (c) 2009 Seb Ruiz * - * * - * This program is free software; you can redistribute it and/or modify it under * - * the terms of the GNU General Public License as published by the Free Software * - * Foundation; either version 2 of the License, or (at your option) any later * - * version. * - * * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY * - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * - * PARTICULAR PURPOSE. See the GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License along with * - * this program. If not, see . * - ****************************************************************************************/ - -#define DEBUG_PREFIX "LyricsManager" - -#include "LyricsManager.h" - -#include "EngineController.h" -#include "core/meta/Meta.h" -#include "core/support/Debug.h" -#include "core-impl/collections/support/CollectionManager.h" - -#include -#include - -#include - -//////////////////////////////////////////////////////////////// -//// CLASS LyricsObserver -/////////////////////////////////////////////////////////////// - -LyricsObserver::LyricsObserver() - : m_subject( 0 ) -{ - qRegisterMetaType("LyricsData"); -} - -LyricsObserver::LyricsObserver( LyricsSubject *s ) - : m_subject( s ) -{ - m_subject->attach( this ); -} - -LyricsObserver::~LyricsObserver() -{ - if( m_subject ) - m_subject->detach( this ); -} - -//////////////////////////////////////////////////////////////// -//// CLASS LyricsSubject -/////////////////////////////////////////////////////////////// - -void LyricsSubject::sendNewLyrics( const LyricsData &lyrics ) -{ - DEBUG_BLOCK - foreach( LyricsObserver* obs, m_observers ) - { - obs->newLyrics( lyrics ); - } -} - -void LyricsSubject::sendNewSuggestions( const QVariantList &sug ) -{ - DEBUG_BLOCK - foreach( LyricsObserver* obs, m_observers ) - { - obs->newSuggestions( sug ); - } -} - -void LyricsSubject::sendLyricsMessage( const QString &key, const QString &val ) -{ - DEBUG_BLOCK - foreach( LyricsObserver* obs, m_observers ) - { - obs->lyricsMessage( key, val ); - } -} - -void LyricsSubject::attach( LyricsObserver *obs ) -{ - if( !obs || m_observers.indexOf( obs ) != -1 ) - return; - m_observers.append( obs ); -} - -void LyricsSubject::detach( LyricsObserver *obs ) -{ - int index = m_observers.indexOf( obs ); - if( index != -1 ) m_observers.removeAt( index ); -} - -//////////////////////////////////////////////////////////////// -//// CLASS LyricsManager -/////////////////////////////////////////////////////////////// - -LyricsManager* LyricsManager::s_self = 0; - -void -LyricsManager::lyricsResult( const QString& lyricsXML, bool cached ) //SLOT -{ - DEBUG_BLOCK - Q_UNUSED( cached ); - - QXmlStreamReader xml( lyricsXML ); - while( xml.readNextStartElement() ) - { - if( xml.name() == QLatin1String("lyric") ) - { - Meta::TrackPtr currentTrack = The::engineController()->currentTrack(); - if( !currentTrack ) - return; - - QString lyrics( xml.readElementText() ); - if( !isEmpty( lyrics ) ) - { - // overwrite cached lyrics (as either there were no lyircs available previously OR - // the user exlicitly agreed to overwrite the lyrics) - debug() << "setting cached lyrics..."; - currentTrack->setCachedLyrics( lyrics ); // TODO: setLyricsByPath? - } - else if( !isEmpty( currentTrack->cachedLyrics() ) ) - { - // we found nothing, so if we have cached lyrics, use it! - debug() << "using cached lyrics..."; - lyrics = currentTrack->cachedLyrics(); - } - else - { - lyricsError( i18n("Retrieved lyrics is empty") ); - return; - } - - QString artist = currentTrack->artist() ? currentTrack->artist()->name() : QString(); - LyricsData data = { lyrics, currentTrack->name(), artist, QUrl() }; - sendNewLyrics( data ); - return; - } - else if( xml.name() == QLatin1String("suggestions") ) - { - QVariantList suggestions; - while( xml.readNextStartElement() ) - { - if( xml.name() != QLatin1String("suggestion") ) - continue; - - const QXmlStreamAttributes &a = xml.attributes(); - - QString artist = a.value( QLatin1String("artist") ).toString(); - QString title = a.value( QLatin1String("title") ).toString(); - QString url = a.value( QLatin1String("url") ).toString(); - - if( !url.isEmpty() ) - suggestions << ( QStringList() << title << artist << url ); - xml.skipCurrentElement(); - } - - debug() << "got" << suggestions.size() << "suggestions"; - if( suggestions.isEmpty() ) - sendLyricsMessage( "notFound", "notfound" ); - else - sendNewSuggestions( suggestions ); - return; - } - xml.skipCurrentElement(); - } - - if( xml.hasError() ) - { - warning() << "errors occurred during reading lyrics xml result:" << xml.errorString(); - lyricsError( i18n("Lyrics data could not be parsed") ); - } -} - -void -LyricsManager::lyricsResultHtml( const QString& lyricsHTML, bool cached ) -{ - DEBUG_BLOCK - Q_UNUSED( cached ) - - // we don't need to deal with suggestions here, because - // we assume the script has called showLyrics if they could - // be suggestions. this is for HTML display only - - Meta::TrackPtr currentTrack = The::engineController()->currentTrack(); - if( currentTrack && !isEmpty( lyricsHTML ) ) - { - QString artist = currentTrack->artist() ? currentTrack->artist()->name() : QString(); - LyricsData data = { lyricsHTML, currentTrack->name(), artist, QUrl() }; - sendNewLyrics( data ); - - // overwrite cached lyrics (as either there were no lyircs available previously OR - // the user exlicitly agreed to overwrite the lyrics) - currentTrack->setCachedLyrics( lyricsHTML ); - } -} - -void -LyricsManager::lyricsError( const QString &error ) -{ - DEBUG_BLOCK - if( !showCached() ) - { - sendLyricsMessage( "error", error ); - } -} - - -void -LyricsManager::lyricsNotFound( const QString& notfound ) -{ - DEBUG_BLOCK - if( !showCached() ) - sendLyricsMessage( "notfound", notfound ); -} - - -bool LyricsManager::showCached() -{ - DEBUG_BLOCK - //if we have cached lyrics there is absolutely no point in not showing these.. - Meta::TrackPtr currentTrack = The::engineController()->currentTrack(); - if( currentTrack && !isEmpty( currentTrack->cachedLyrics() ) ) - { - // TODO: add some sort of feedback that we could not fetch new ones - // so we are showing a cached result - debug() << "showing cached lyrics!"; - - QString lyrics = currentTrack->cachedLyrics(); - QString artist = currentTrack->artist() ? currentTrack->artist()->name() : QString(); - LyricsData data = { lyrics, currentTrack->name(), artist, QUrl() }; - sendNewLyrics( data ); - return true; - } - return false; -} - -void LyricsManager::setLyricsForTrack( const QString &trackUrl, const QString &lyrics ) const -{ - DEBUG_BLOCK - - Meta::TrackPtr track = CollectionManager::instance()->trackForUrl( QUrl( trackUrl ) ); - - if( track ) - track->setCachedLyrics( lyrics ); - else - debug() << QString("could not find a track for the given URL (%1) - ignoring.").arg( trackUrl ); -} - -bool LyricsManager::isEmpty( const QString &lyrics ) const -{ - QTextEdit testItem; - - // Set the text of the TextItem. - if( Qt::mightBeRichText( lyrics ) ) - testItem.setHtml( lyrics ); - else - testItem.setPlainText( lyrics ); - - // Get the plaintext content. - // We use toPlainText() to strip all Html formatting, - // so we can test if there's any text given. - QString testText = testItem.toPlainText().trimmed(); - - return testText.isEmpty(); -} diff --git a/src/context/LyricsManager.h b/src/context/LyricsManager.h deleted file mode 100644 index dddd626bd5..0000000000 --- a/src/context/LyricsManager.h +++ /dev/null @@ -1,129 +0,0 @@ -/**************************************************************************************** - * Copyright (c) 2007 Leo Franchi * - * * - * This program is free software; you can redistribute it and/or modify it under * - * the terms of the GNU General Public License as published by the Free Software * - * Foundation; either version 2 of the License, or (at your option) any later * - * version. * - * * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY * - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * - * PARTICULAR PURPOSE. See the GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License along with * - * this program. If not, see . * - ****************************************************************************************/ - -#ifndef LYRICS_MANAGER_H -#define LYRICS_MANAGER_H - -#include "amarok_export.h" - -#include -#include -#include - -class LyricsSubject; - -struct LyricsData -{ - QString text; - QString title; - QString artist; - QUrl site; - - void clear() - { - text.clear(); - title.clear(); - artist.clear(); - site.clear(); - } -}; - -class AMAROK_EXPORT LyricsObserver -{ - public: - LyricsObserver(); - explicit LyricsObserver( LyricsSubject* ); - virtual ~LyricsObserver(); - - /** - * A lyrics script has returned new lyrics. - */ - virtual void newLyrics( const LyricsData &lyrics ) { Q_UNUSED( lyrics ); } - /** - * A lyrics script has returned a list of suggested URLs for correct lyrics. - */ - virtual void newSuggestions( const QVariantList &suggestions ) { Q_UNUSED( suggestions ); } - /** - * A lyrics script has returned some generic message that they want to be displayed. - */ - virtual void lyricsMessage( const QString& key, const QString &val ) { Q_UNUSED( key ); Q_UNUSED( val ); } - - private: - LyricsSubject *m_subject; -}; - -class LyricsSubject -{ - public: - void attach( LyricsObserver *observer ); - void detach( LyricsObserver *observer ); - - protected: - LyricsSubject() {} - virtual ~LyricsSubject() {} - - void sendNewLyrics( const LyricsData &lyrics ); - void sendNewSuggestions( const QVariantList &suggestions ); - void sendLyricsMessage( const QString &key, const QString &val ); - - private: - QList m_observers; -}; - -class AMAROK_EXPORT LyricsManager : public LyricsSubject -{ - public: - static LyricsManager* self() - { - if( !s_self ) - s_self = new LyricsManager(); - - return s_self; - } - - void lyricsResult( const QString& lyrics, bool cached = false ); - void lyricsResultHtml( const QString& lyrics, bool cached = false ); - void lyricsError( const QString &error ); - void lyricsNotFound( const QString& notfound ); - - /** - * Sets the given lyrics for the track with the given URL. - * - * @param trackUrl The URL of the track. - * @param lyrics The new lyrics. - */ - void setLyricsForTrack( const QString &trackUrl, const QString &lyrics ) const; - - /** - * Tests if the given lyrics are empty. - * - * @param lyrics The lyrics which will be tested. - * - * @return true if the given lyrics are empty, otherwise false. - */ - bool isEmpty( const QString &lyrics ) const; - - private: - LyricsManager() : LyricsSubject() { s_self = this; } - - bool showCached(); - - static LyricsManager* s_self; -}; - -Q_DECLARE_METATYPE( LyricsData ); - -#endif diff --git a/src/context/applets/albums/plugin/AlbumItem.h b/src/context/applets/albums/plugin/AlbumItem.h index 84c3267667..d1f41329a1 100644 --- a/src/context/applets/albums/plugin/AlbumItem.h +++ b/src/context/applets/albums/plugin/AlbumItem.h @@ -1,82 +1,82 @@ /**************************************************************************************** * Copyright (c) 2008 Seb Ruiz * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #ifndef AMAROK_ALBUMITEM_H #define AMAROK_ALBUMITEM_H #include "core/meta/Observer.h" #include #include #include class AlbumItem : public QObject, public QStandardItem, public Meta::Observer { Q_OBJECT public: AlbumItem(); ~AlbumItem(); /** * Sets the AlbumPtr for this item to associate with * * @arg album pointer to associate with */ void setAlbum( Meta::AlbumPtr albumPtr ); /** * @return the album pointer associated with this item */ Meta::AlbumPtr album() const { return m_album; } /** * Sets the size of the album art to display */ void setIconSize( const int iconSize ); /** * @return the size of the album art */ int iconSize() const { return m_iconSize; } /** * Setter to determine whether the item should show the Artist as well as the * album name. Used for 'recent albums' listing. */ void setShowArtist( const bool showArtist ); // overloaded from Meta::Observer using Observer::metadataChanged; - virtual void metadataChanged( Meta::AlbumPtr album ) Q_DECL_OVERRIDE; + virtual void metadataChanged( Meta::AlbumPtr album ) override; - virtual int type() const Q_DECL_OVERRIDE; + virtual int type() const override; - virtual bool operator<( const QStandardItem &other ) const Q_DECL_OVERRIDE; + virtual bool operator<( const QStandardItem &other ) const override; private Q_SLOTS: /** Updates the item after metadataChanged was called. We need this indirection to get executed by the UI thread. */ void update(); private: Meta::AlbumPtr m_album; int m_iconSize; bool m_showArtist; }; #endif // multiple inclusion guard diff --git a/src/context/applets/albums/plugin/AlbumsModel.h b/src/context/applets/albums/plugin/AlbumsModel.h index 00dccbfabb..5166eee008 100644 --- a/src/context/applets/albums/plugin/AlbumsModel.h +++ b/src/context/applets/albums/plugin/AlbumsModel.h @@ -1,91 +1,91 @@ /**************************************************************************************** * Copyright (c) 2008 Andreas Muetzel * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General 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_ALBUMSMODEL_H #define AMAROK_ALBUMSMODEL_H #include "core/meta/forward_declarations.h" #include #include /** * This Model is used to get the right mime type/data for entries in the albums treeview */ class AlbumsModel : public QStandardItemModel { Q_OBJECT public: explicit AlbumsModel( QObject *parent = 0 ); virtual ~AlbumsModel() {} virtual QVariant data( const QModelIndex &index, int role ) const; virtual QMimeData* mimeData( const QModelIndexList &indices ) const; virtual QStringList mimeTypes() const; int rowHeight() const; private Q_SLOTS: void updateRowHeight(); private: Meta::TrackList tracksForIndex( const QModelIndex &index ) const; int m_rowHeight; }; class QCollator; class AlbumsProxyModel : public QSortFilterProxyModel { Q_OBJECT Q_PROPERTY( Mode mode READ mode WRITE setMode NOTIFY modeChanged ) public: explicit AlbumsProxyModel( QObject *parent ); ~AlbumsProxyModel(); enum Mode { SortByCreateDate, SortByYear }; Q_ENUM( Mode ) Mode mode() const; void setMode( Mode mode ); - QHash roleNames() const Q_DECL_OVERRIDE; + virtual QHash roleNames() const override; signals: void modeChanged(); protected: /** * Determine if album @param left is less than album @param right. * * If @param left and @param right both reference albums and @c m_mode * is set to @c SortByCreateDate, @c lessThan will return @c true if * and only the album referenced by @param left has a track that was * added more recently than all of the tracks in the album * referenced by @param right. */ - bool lessThan( const QModelIndex &left, const QModelIndex &right ) const; + virtual bool lessThan( const QModelIndex &left, const QModelIndex &right ) const override; - bool filterAcceptsRow( int sourceRow, const QModelIndex &sourceParent ) const Q_DECL_OVERRIDE; + virtual bool filterAcceptsRow( int sourceRow, const QModelIndex &sourceParent ) const override; private: Mode m_mode; QCollator *m_collator; }; Q_DECLARE_METATYPE( AlbumsProxyModel* ) #endif diff --git a/src/context/applets/albums/plugin/AlbumsPlugin.cpp b/src/context/applets/albums/plugin/AlbumsPlugin.cpp index 1e9611188e..e183215135 100644 --- a/src/context/applets/albums/plugin/AlbumsPlugin.cpp +++ b/src/context/applets/albums/plugin/AlbumsPlugin.cpp @@ -1,52 +1,52 @@ /* * Copyright 2017 Malte Veerman * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "AlbumsEngine.h" #include #include class AlbumsPlugin : public QQmlExtensionPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid) public: - void registerTypes(const char* uri) Q_DECL_OVERRIDE + void registerTypes(const char* uri) override { Q_ASSERT(uri == QLatin1String("org.kde.amarok.albums")); // qmlRegisterUncreatableType(); qmlRegisterSingletonType(uri, 1, 0, "AlbumsEngine", albums_engine_provider); } static QObject *albums_engine_provider(QQmlEngine *engine, QJSEngine *scriptEngine) { Q_UNUSED(engine) Q_UNUSED(scriptEngine) return new AlbumsEngine(); } }; #include diff --git a/src/context/applets/albums/plugin/TrackItem.h b/src/context/applets/albums/plugin/TrackItem.h index 3f4e6c3f0d..ca692563a6 100644 --- a/src/context/applets/albums/plugin/TrackItem.h +++ b/src/context/applets/albums/plugin/TrackItem.h @@ -1,68 +1,68 @@ /**************************************************************************************** * Copyright (c) 2008 Seb Ruiz * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #ifndef AMAROK_TRACKITEM_H #define AMAROK_TRACKITEM_H #include "core/meta/Observer.h" #include #include class TrackItem : public QStandardItem, public Meta::Observer { public: TrackItem(); ~TrackItem(); /** * Sets the TrackPtr for this item to associate with * * @arg track pointer to associate with */ void setTrack( Meta::TrackPtr trackPtr ); /** * @return the track pointer associated with this item */ Meta::TrackPtr track() const { return m_track; } /** * Applies an italic style if the track is the currently * playing track */ void italicise(); /** * Applies a bold style if the track is owned by the currently * playing artist */ void bold(); // overloaded from Meta::Observer using Observer::metadataChanged; - virtual void metadataChanged( Meta::TrackPtr track ) Q_DECL_OVERRIDE; + virtual void metadataChanged( Meta::TrackPtr track ) override; - virtual int type() const Q_DECL_OVERRIDE; + virtual int type() const override; - virtual bool operator<( const QStandardItem &other ) const Q_DECL_OVERRIDE; + virtual bool operator<( const QStandardItem &other ) const override; private: Meta::TrackPtr m_track; QMutex m_mutex; }; #endif // multiple inclusion guard diff --git a/src/context/applets/analyzer/plugin/AnalyzerBase.cpp b/src/context/applets/analyzer/plugin/AnalyzerBase.cpp index e1cc04a57d..4d13ce55ce 100644 --- a/src/context/applets/analyzer/plugin/AnalyzerBase.cpp +++ b/src/context/applets/analyzer/plugin/AnalyzerBase.cpp @@ -1,239 +1,239 @@ /**************************************************************************************** * Copyright (c) 2003 Max Howell * * Copyright (c) 2009 Martin Sandsmark * * Copyright (c) 2013 Mark Kretschmann * * Copyright (c) 2017 Malte Veerman * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General 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 "AnalyzerBase.h" #include "AnalyzerWorker.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include "EngineController.h" #include "MainWindow.h" #include #ifdef Q_WS_X11 #include #endif #include #include // INSTRUCTIONS // 1. Reimplement QQuickFramebufferObject::createRenderer(). // 2. Reimplement Analyzer::Base::createWorker(). // 3. Set your preferred scope width with setScopeSize(). Analyzer::Base::Base( QQuickItem *parent ) : QQuickFramebufferObject( parent ) , m_sampleRate( 44100 ) , m_scopeSize( 0 ) , m_worker( Q_NULLPTR ) { DEBUG_BLOCK - qRegisterMetaType(); + qRegisterMetaType("WindowFunction"); m_minFreq = config().readEntry( "minFreq", 50.0 ); m_maxFreq = config().readEntry( "maxFreq", 15000.0 ); connect( The::engineController(), &EngineController::trackChanged, this, &Base::refreshSampleRate ); connect( The::engineController(), &EngineController::trackMetadataChanged, this, &Base::refreshSampleRate ); #ifdef Q_WS_X11 connect( KWindowSystem::self(), &KWindowSystem::currentDesktopChanged, this, &Base::currentDesktopChanged ); #endif QTimer::singleShot( 0, this, &Base::connectSignals ); } Analyzer::Base::~Base() { DEBUG_BLOCK m_worker->deleteLater(); m_worker = Q_NULLPTR; m_workerThread.quit(); m_workerThread.wait(); } void Analyzer::Base::connectSignals() { DEBUG_BLOCK if( !m_worker ) { m_worker = createWorker(); m_worker->setSampleSize( sampleSize() ); m_worker->setScopeSize( m_scopeSize ); m_worker->setWindowFunction( windowFunction() ); m_worker->moveToThread( &m_workerThread ); m_workerThread.start(); connect( this, &Base::calculateExpFactorNeeded, m_worker, &Worker::calculateExpFactor ); connect( this, &Base::windowFunctionChanged, m_worker, &Worker::setWindowFunction ); connect( this, &Base::sampleSizeChanged, m_worker, &Worker::setSampleSize ); connect( this, &Base::scopeSizeChanged, m_worker, &Worker::setScopeSize ); connect( The::engineController(), &EngineController::playbackStateChanged, m_worker, &Worker::playbackStateChanged ); connect( The::engineController(), &EngineController::audioDataReady, m_worker, &Worker::receiveData, Qt::DirectConnection ); setSampleSize( config().readEntry( "sampleSize", 4096 ) ); setWindowFunction( (WindowFunction) config().readEntry( "windowFunction", (int)Hann ) ); emit calculateExpFactorNeeded( m_minFreq, m_maxFreq, m_sampleRate); } } void Analyzer::Base::disconnectSignals() { DEBUG_BLOCK if( m_worker ) disconnect( The::engineController(), &EngineController::audioDataReady, m_worker, &Worker::receiveData ); } void Analyzer::Base::currentDesktopChanged() { // Optimization for X11/Linux desktops: // Don't update the analyzer if Amarok is not on the active virtual desktop. if( The::mainWindow()->isOnCurrentDesktop() ) connectSignals(); else disconnectSignals(); } void Analyzer::Base::refreshSampleRate() { const auto currentTrack = The::engineController()->currentTrack(); int sampleRate = currentTrack ? currentTrack->sampleRate() : 44100; if( m_sampleRate == sampleRate ) return; m_sampleRate = sampleRate; emit calculateExpFactorNeeded( m_minFreq, m_maxFreq, m_sampleRate ); } KConfigGroup Analyzer::Base::config() const { return Amarok::config( QStringLiteral( "Context" ) ).group( "Analyzer" ); } void Analyzer::Base::setScopeSize( int scopeSize ) { if( scopeSize <= 0 ) { debug() << "Scope size must be greater than zero"; return; } if( m_scopeSize == scopeSize ) return; m_scopeSize = scopeSize; emit scopeSizeChanged( scopeSize ); emit calculateExpFactorNeeded( m_minFreq, m_maxFreq, m_sampleRate ); } void Analyzer::Base::setMaxFreq( qreal maxFreq ) { DEBUG_BLOCK debug() << "Set maximum frequency to:" << maxFreq; if( m_maxFreq == maxFreq || maxFreq < 0.0 ) return; config().writeEntry( "maxFreq", maxFreq ); m_maxFreq = maxFreq; emit maxFreqChanged(); emit calculateExpFactorNeeded( m_minFreq, m_maxFreq, m_sampleRate ); } void Analyzer::Base::setMinFreq( qreal minFreq ) { DEBUG_BLOCK debug() << "Set minimum frequency to:" << minFreq; if( m_minFreq == minFreq || minFreq <= 0.0 ) return; config().writeEntry( "minFreq", minFreq ); m_minFreq = minFreq; emit minFreqChanged(); emit calculateExpFactorNeeded( m_minFreq, m_maxFreq, m_sampleRate ); } Analyzer::Base::WindowFunction Analyzer::Base::windowFunction() const { return (WindowFunction)config().readEntry( "windowFunction", (int)Hann ); } void Analyzer::Base::setWindowFunction( WindowFunction windowFunction ) { DEBUG_BLOCK debug() << "Set window function to:" << windowFunction; config().writeEntry( "windowFunction", (int)windowFunction ); emit windowFunctionChanged( windowFunction ); } int Analyzer::Base::sampleSize() const { return config().readEntry( "sampleSize", 2048 ); } void Analyzer::Base::setSampleSize( uint sampleSize ) { DEBUG_BLOCK debug() << "Set sample size to:" << sampleSize; if( sampleSize < (int) EngineController::DATAOUTPUT_DATA_SIZE ) { warning() << "Sample size must be at least" << EngineController::DATAOUTPUT_DATA_SIZE; sampleSize = EngineController::DATAOUTPUT_DATA_SIZE; } config().writeEntry( "sampleSize", sampleSize ); emit sampleSizeChanged( sampleSize ); emit calculateExpFactorNeeded( m_minFreq, m_maxFreq, m_sampleRate ); } const Analyzer::Worker * Analyzer::Base::worker() const { return const_cast( m_worker ); } diff --git a/src/context/applets/analyzer/plugin/AnalyzerBase.h b/src/context/applets/analyzer/plugin/AnalyzerBase.h index 47eb1546ba..609623bdff 100644 --- a/src/context/applets/analyzer/plugin/AnalyzerBase.h +++ b/src/context/applets/analyzer/plugin/AnalyzerBase.h @@ -1,127 +1,127 @@ /**************************************************************************************** * Copyright (c) 2004 Max Howell * * Copyright (c) 2009 Martin Sandsmark * * Copyright (c) 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 ANALYZERBASE_H #define ANALYZERBASE_H #ifdef __FreeBSD__ #include #endif #include #include #include #include #include #include namespace Analyzer { class Worker; class Base : public QQuickFramebufferObject { Q_OBJECT Q_PROPERTY(qreal minFrequency READ minFreq WRITE setMinFreq NOTIFY minFreqChanged) Q_PROPERTY(qreal maxFrequency READ maxFreq WRITE setMaxFreq NOTIFY maxFreqChanged) Q_PROPERTY(WindowFunction windowFunction READ windowFunction WRITE setWindowFunction NOTIFY windowFunctionChanged) Q_PROPERTY(qreal minimumFrequency READ minFreq WRITE setMinFreq NOTIFY minFreqChanged) Q_PROPERTY(qreal maximumFrequency READ maxFreq WRITE setMaxFreq NOTIFY maxFreqChanged) Q_PROPERTY(int sampleSize READ sampleSize WRITE setSampleSize NOTIFY sampleSizeChanged) public: enum WindowFunction { - Rectangular = 0, - Hann = 1, - Nuttall = 2, - Lanczos = 3, - Sine = 4 + Rectangular, + Hann, + Nuttall, + Lanczos, + Sine }; Q_ENUM(WindowFunction) static const int DEMO_INTERVAL = 20; // ~50 fps virtual ~Base(); qreal maxFreq() const { return m_maxFreq; } void setMaxFreq( qreal maxFreq ); qreal minFreq() const { return m_minFreq; } void setMinFreq( qreal minFreq ); WindowFunction windowFunction() const; void setWindowFunction( WindowFunction windowFunction ); int sampleSize() const; void setSampleSize( uint sampleSize ); /** * Returns the worker object associated with this analyzer. */ const Worker* worker() const; signals: void minFreqChanged(); void maxFreqChanged(); void scopeSizeChanged( uint ); void windowFunctionChanged( WindowFunction ); void sampleSizeChanged( uint ); void calculateExpFactorNeeded( qreal, qreal, uint ); protected: Base( QQuickItem* ); /** * Creates a new worker instance. * Subclasses must implement this function. * All compute heavy tasks should be offloaded to the created worker. * If you make any connections with your worker, remember to make them queued connections. * Do not set a parent for the worker. Base will take ownership of it. */ virtual Worker* createWorker() const = 0; /** * Returns the standard KConfigGroup for all analyzers. * You can reimplement this function, if you want your subclass to have a different config. */ virtual KConfigGroup config() const; /** * Use this function to set the size for the scope computed by the worker. */ void setScopeSize( int size ); private: void connectSignals(); void disconnectSignals(); void currentDesktopChanged(); void refreshSampleRate(); double m_minFreq, m_maxFreq; int m_sampleRate; int m_scopeSize; Worker *m_worker; QThread m_workerThread; }; } //END namespace Analyzer #endif diff --git a/src/context/applets/analyzer/plugin/AnalyzerPlugin.cpp b/src/context/applets/analyzer/plugin/AnalyzerPlugin.cpp index c12051a4f7..07fa96f84d 100644 --- a/src/context/applets/analyzer/plugin/AnalyzerPlugin.cpp +++ b/src/context/applets/analyzer/plugin/AnalyzerPlugin.cpp @@ -1,43 +1,43 @@ /* * Copyright 2017 Malte Veerman * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "BlockAnalyzer.h" #include class AnalyzerPlugin : public QQmlExtensionPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid) public: - void registerTypes(const char* uri) Q_DECL_OVERRIDE + void registerTypes(const char* uri) override { Q_ASSERT(uri == QLatin1String("org.kde.amarok.analyzer")); qmlRegisterUncreatableType(uri, 1, 0, "Analyzer", QStringLiteral("Analyzer is an uncreatable type. Use its derived classes instead")); qmlRegisterType(uri, 1, 0, "BlockAnalyzer"); } }; #include diff --git a/src/context/applets/analyzer/plugin/BlockAnalyzer.h b/src/context/applets/analyzer/plugin/BlockAnalyzer.h index 0379316a15..0a0138a747 100644 --- a/src/context/applets/analyzer/plugin/BlockAnalyzer.h +++ b/src/context/applets/analyzer/plugin/BlockAnalyzer.h @@ -1,100 +1,100 @@ /**************************************************************************************** * Copyright (c) 2003-2005 Max Howell * * Copyright (c) 2005-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 Pulic License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #ifndef BLOCKANALYZER_H #define BLOCKANALYZER_H #include "AnalyzerBase.h" #include #include #include #include #include class QPalette; class QSGTexture; class BlockAnalyzer : public Analyzer::Base { friend class BlockRenderer; Q_OBJECT Q_PROPERTY(FallSpeed fallSpeed READ fallSpeed WRITE setFallSpeed NOTIFY fallSpeedChanged) Q_PROPERTY(int columnWidth READ columnWidth WRITE setColumnWidth NOTIFY columnWidthChanged) Q_PROPERTY(bool showFadebars READ showFadebars WRITE setShowFadebars NOTIFY showFadebarsChanged) public: enum FallSpeed { VerySlow = 0, Slow = 1, Medium = 2, Fast = 3, VeryFast = 4 }; Q_ENUM( FallSpeed ) explicit BlockAnalyzer( QQuickItem *parent = Q_NULLPTR ); - Renderer* createRenderer() const Q_DECL_OVERRIDE; + Renderer* createRenderer() const override; FallSpeed fallSpeed() const { return m_fallSpeed; } void setFallSpeed( FallSpeed fallSpeed ); int columnWidth() const { return m_columnWidth; } void setColumnWidth( int columnWidth ); bool showFadebars() const { return m_showFadebars; } void setShowFadebars( bool showFadebars ); // Signed ints because most of what we compare them against are ints static const int BLOCK_HEIGHT = 2; static const int FADE_SIZE = 90; signals: void fallSpeedChanged(); void columnWidthChanged(); void showFadebarsChanged( bool ); void stepChanged( qreal ); void rowsChanged( int ); void columnsChanged( int ); void refreshRateChanged( qreal ); protected: - virtual void geometryChanged(const QRectF& newGeometry, const QRectF& oldGeometry) Q_DECL_OVERRIDE; - virtual Analyzer::Worker* createWorker() const Q_DECL_OVERRIDE; + virtual void geometryChanged(const QRectF& newGeometry, const QRectF& oldGeometry) override; + virtual Analyzer::Worker* createWorker() const override; virtual void paletteChange( const QPalette& ); void drawBackground( const QPalette &palette ); void determineStep(); private: void newWindow( QQuickWindow *window ); int m_columns, m_rows; //number of rows and columns of blocks int m_columnWidth; bool m_showFadebars; QPixmap m_barPixmap; QPixmap m_topBarPixmap; QPixmap m_backgroundPixmap; QVector m_fadeBarsPixmaps; bool m_pixmapsChanged; qreal m_step; //rows to fall per frame FallSpeed m_fallSpeed; }; #endif diff --git a/src/context/applets/analyzer/plugin/BlockRenderer.h b/src/context/applets/analyzer/plugin/BlockRenderer.h index 5d38c9a057..930f5748f2 100644 --- a/src/context/applets/analyzer/plugin/BlockRenderer.h +++ b/src/context/applets/analyzer/plugin/BlockRenderer.h @@ -1,130 +1,130 @@ /* * Copyright (c) 2003-2005 Max Howell * Copyright (c) 2005-2013 Mark Kretschmann * Copyright 2017 Malte Veerman * * This program is free software; you can redistribute it and/or * modify it under the 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 BLOCKRENDERER_H #define BLOCKRENDERER_H #include "BlockAnalyzer.h" #include "BlockWorker.h" #include "core/support/Debug.h" #include #include #include #include #include #include class BlockRenderer : public QQuickFramebufferObject::Renderer { public: static const int BLOCK_HEIGHT = BlockAnalyzer::BLOCK_HEIGHT; BlockRenderer() {} protected: - QOpenGLFramebufferObject* createFramebufferObject(const QSize &size) Q_DECL_OVERRIDE + QOpenGLFramebufferObject* createFramebufferObject(const QSize &size) override { QOpenGLFramebufferObject* fo = new QOpenGLFramebufferObject(size); fo->setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); return fo; } - void render() Q_DECL_OVERRIDE + void render() override { QOpenGLPaintDevice d; d.setSize(framebufferObject()->size()); QPainter p(&d); // Draw the background p.drawPixmap(QRect(QPoint(0, 0), framebufferObject()->size()), m_backgroundPixmap); const int frameHeight = framebufferObject()->height(); for( uint x = 0; x < (uint)m_store.size(); ++x ) { // Draw fade bars for( const auto &fadebar : m_fadebars.at(x) ) { if( fadebar.intensity > 0 ) { const uint offset = fadebar.intensity; const int fadeHeight = frameHeight - fadebar.y * (BLOCK_HEIGHT + 1); if( fadeHeight > 0 ) p.drawPixmap(x * ( m_columnWidth + 1 ), 0, m_fadeBarsPixmaps.value(offset), 0, 0, m_columnWidth, fadeHeight); } } // Draw bars const int height = frameHeight - m_store.at(x) * (BLOCK_HEIGHT + 1); if (height > 0) p.drawPixmap(x * (m_columnWidth + 1), 0, m_barPixmap, 0, 0, m_columnWidth, height); // Draw top bar p.drawPixmap(x * (m_columnWidth + 1), height + BLOCK_HEIGHT - 1, m_topBarPixmap); } } - void synchronize(QQuickFramebufferObject *item) Q_DECL_OVERRIDE + void synchronize(QQuickFramebufferObject *item) override { auto analyzer = qobject_cast(item); if (!analyzer) return; m_rows = analyzer->m_rows; m_columnWidth = analyzer->m_columnWidth; auto worker = qobject_cast(analyzer->worker()); if (worker) { worker->m_mutex.lock(); m_store = worker->m_store; m_fadebars = worker->m_fadebars; worker->m_mutex.unlock(); } if (analyzer->m_pixmapsChanged) { m_barPixmap = analyzer->m_barPixmap; m_topBarPixmap = analyzer->m_topBarPixmap; m_backgroundPixmap = analyzer->m_backgroundPixmap; m_fadeBarsPixmaps = analyzer->m_fadeBarsPixmaps; analyzer->m_pixmapsChanged = false; } } private: QVector m_store; QVector > m_fadebars; int m_rows; int m_columnWidth; QPixmap m_barPixmap; QPixmap m_topBarPixmap; QPixmap m_backgroundPixmap; QVector m_fadeBarsPixmaps; }; #endif //BLOCKRENDERER_H diff --git a/src/context/applets/analyzer/plugin/BlockWorker.h b/src/context/applets/analyzer/plugin/BlockWorker.h index a24029f488..39671035a5 100644 --- a/src/context/applets/analyzer/plugin/BlockWorker.h +++ b/src/context/applets/analyzer/plugin/BlockWorker.h @@ -1,76 +1,76 @@ /**************************************************************************************** * Copyright (c) 2017 Malte Veerman * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Pulic License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #ifndef BLOCKWORKER_H #define BLOCKWORKER_H #include "AnalyzerWorker.h" #include #include #include class BlockWorker : public Analyzer::Worker { friend class BlockRenderer; Q_OBJECT public: BlockWorker( int rows, int columns, qreal step, bool showFadebars ); void setStep( qreal step ) { m_step = step; } void setRows( int rows ); void setColumns( int columns ); void setRefreshRate( qreal refreshRate ) { m_refreshTime = std::floor( 1000.0 / refreshRate ); } void setShowFadebars( bool showFadebars ) { m_showFadebars = showFadebars; } signals: void finished(); protected: - void analyze() Q_DECL_OVERRIDE; + void analyze() override; private: struct Fadebar { int y; qreal intensity; Fadebar() { y = 0; intensity = 0.0; } Fadebar( int y, qreal intensity ) { this->y = y; this->intensity = intensity; } }; mutable QMutex m_mutex; //used to lock m_store and m_fadebars QVector m_store; //current bar heights QVector m_yscale; QVector > m_fadebars; qreal m_step; int m_rows; int m_columns; int m_refreshTime; bool m_showFadebars; QTime m_lastUpdate; }; #endif //BLOCKWORKER_H diff --git a/src/context/applets/currenttrack/plugin/CurrentPlugin.cpp b/src/context/applets/currenttrack/plugin/CurrentPlugin.cpp index 6334ba82dd..7651ebe5fe 100644 --- a/src/context/applets/currenttrack/plugin/CurrentPlugin.cpp +++ b/src/context/applets/currenttrack/plugin/CurrentPlugin.cpp @@ -1,51 +1,51 @@ /* * Copyright 2017 Malte Veerman * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "CurrentEngine.h" #include #include class CurrentPlugin : public QQmlExtensionPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid) public: - void registerTypes(const char* uri) Q_DECL_OVERRIDE + void registerTypes(const char* uri) override { Q_ASSERT(uri == QLatin1String("org.kde.amarok.currenttrack")); qmlRegisterSingletonType(uri, 1, 0, "CurrentTrackEngine", current_engine_provider); } static QObject *current_engine_provider(QQmlEngine *engine, QJSEngine *scriptEngine) { Q_UNUSED(engine) Q_UNUSED(scriptEngine) return new CurrentEngine(); } }; #include diff --git a/src/context/applets/info/plugin/InfoPlugin.cpp b/src/context/applets/info/plugin/InfoPlugin.cpp index c60d3bed36..2004c30ace 100644 --- a/src/context/applets/info/plugin/InfoPlugin.cpp +++ b/src/context/applets/info/plugin/InfoPlugin.cpp @@ -1,51 +1,51 @@ /* * Copyright 2017 Malte Veerman * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "InfoEngine.h" #include #include class InfoPlugin : public QQmlExtensionPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid) public: - void registerTypes(const char* uri) Q_DECL_OVERRIDE + void registerTypes(const char* uri) override { Q_ASSERT(uri == QLatin1String("org.kde.amarok.info")); qmlRegisterSingletonType(uri, 1, 0, "InfoEngine", info_engine_provider); } static QObject *info_engine_provider(QQmlEngine *engine, QJSEngine *scriptEngine) { Q_UNUSED(engine) Q_UNUSED(scriptEngine) return new InfoEngine(); } }; #include diff --git a/src/context/applets/lyrics/plugin/LyricsEngine.cpp b/src/context/applets/lyrics/plugin/LyricsEngine.cpp index 799a6021a7..67bbeeb836 100644 --- a/src/context/applets/lyrics/plugin/LyricsEngine.cpp +++ b/src/context/applets/lyrics/plugin/LyricsEngine.cpp @@ -1,288 +1,188 @@ /**************************************************************************************** * Copyright (c) 2007-2008 Leo Franchi * * Copyright (c) 2008 Mark Kretschmann * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #define DEBUG_PREFIX "LyricsEngine" #include "LyricsEngine.h" #include "EngineController.h" -#include "scripting/scriptmanager/ScriptManager.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" +#include "lyrics/LyricsManager.h" #include #include LyricsEngine::LyricsEngine( QObject* parent ) : QObject( parent ) - , LyricsObserver( LyricsManager::self() ) , m_fetching( false ) , m_isUpdateInProgress( false ) { EngineController* engine = The::engineController(); + LyricsManager* lyricsManager = LyricsManager::instance(); connect( engine, &EngineController::trackChanged, this, &LyricsEngine::update ); connect( engine, &EngineController::trackMetadataChanged, this, &LyricsEngine::onTrackMetadataChanged ); connect( engine, &EngineController::trackPositionChanged, this, &LyricsEngine::positionChanged ); + connect( lyricsManager, &LyricsManager::newLyrics, this, &LyricsEngine::newLyrics ); + connect( lyricsManager, &LyricsManager::newSuggestions, this, &LyricsEngine::newSuggestions ); } void LyricsEngine::onTrackMetadataChanged( Meta::TrackPtr track ) { DEBUG_BLOCK // Only update if the lyrics have changed. - QString artist = track->artist() ? track->artist()->name() : QString(); - if( m_lyrics.artist != artist || - m_lyrics.title != track->name() || - m_lyrics.text != track->cachedLyrics() ) + if( m_lyrics != track->cachedLyrics() ) update(); } void LyricsEngine::update() { - if( m_isUpdateInProgress ) - return; - - m_isUpdateInProgress = true; - - // -- get current title and artist - Meta::TrackPtr currentTrack = The::engineController()->currentTrack(); - if( !currentTrack ) + Meta::TrackPtr track = The::engineController()->currentTrack(); + if( !track ) { - debug() << "no current track"; - m_lyrics.clear(); - emit lyricsChanged(); - m_isUpdateInProgress = false; + clearLyrics(); return; } - QString title = currentTrack->name(); - QString artist = currentTrack->artist() ? currentTrack->artist()->name() : QString(); - - // -- clean up title - const QString magnatunePreviewString = QLatin1String( "PREVIEW: buy it at www.magnatune.com" ); - if( title.contains(magnatunePreviewString, Qt::CaseSensitive) ) - title = title.remove( " (" + magnatunePreviewString + ')' ); - if( artist.contains(magnatunePreviewString, Qt::CaseSensitive) ) - artist = artist.remove( " (" + magnatunePreviewString + ')' ); - - if( title.isEmpty() && currentTrack ) - { - /* If title is empty, try to use pretty title. - The fact that it often (but not always) has "artist name" together, can be bad, - but at least the user will hopefully get nice suggestions. */ - QString prettyTitle = currentTrack->prettyName(); - int h = prettyTitle.indexOf( QLatin1Char('-') ); - if ( h != -1 ) - { - title = prettyTitle.mid( h + 1 ).trimmed(); - if( title.contains(magnatunePreviewString, Qt::CaseSensitive) ) - title = title.remove( " (" + magnatunePreviewString + ')' ); - - if( artist.isEmpty() ) - { - artist = prettyTitle.mid( 0, h ).trimmed(); - if( artist.contains(magnatunePreviewString, Qt::CaseSensitive) ) - artist = artist.remove( " (" + magnatunePreviewString + ')' ); - } - } - } - - LyricsData lyrics = { currentTrack->cachedLyrics(), title, artist, QUrl() }; - - // Check if the title, the artist and the lyrics are still the same. - if( !lyrics.text.isEmpty() && (lyrics.text == m_lyrics.text) ) + if( LyricsManager::instance()->isEmpty( track->cachedLyrics() ) ) { - debug() << "nothing changed:" << lyrics.title; - newLyrics( lyrics ); - m_isUpdateInProgress = false; + clearLyrics(); return; } - // don't rely on caching for streams - const bool cached = !LyricsManager::self()->isEmpty( lyrics.text ) - && !The::engineController()->isStream(); - - if( cached ) - { - newLyrics( lyrics ); - } - else - { - // no lyrics, and no lyrics script! - if( !ScriptManager::instance()->lyricsScriptRunning() ) - { - debug() << "no lyrics script running"; - clearLyrics(); - disconnect( ScriptManager::instance(), &ScriptManager::lyricsScriptStarted, this, 0 ); - connect( ScriptManager::instance(), &ScriptManager::lyricsScriptStarted, this, &LyricsEngine::update ); - m_isUpdateInProgress = false; - return; - } - - // fetch by lyrics script - clearLyrics(); - m_fetching = true; - emit fetchingChanged(); - ScriptManager::instance()->notifyFetchLyrics( lyrics.artist, lyrics.title, "", currentTrack ); - } - m_isUpdateInProgress = false; + newLyrics( track ); } -void LyricsEngine::newLyrics( const LyricsData &lyrics ) +void LyricsEngine::newLyrics( const Meta::TrackPtr &track ) { DEBUG_BLOCK - m_lyrics = lyrics; + if( track != The::engineController()->currentTrack() ) + return; + + m_lyrics = track->cachedLyrics(); emit lyricsChanged(); m_fetching = false; emit fetchingChanged(); } void LyricsEngine::newSuggestions( const QVariantList &suggested ) { DEBUG_BLOCK // each string is in "title - artist " form m_suggestions = suggested; clearLyrics(); } void LyricsEngine::lyricsMessage( const QString& key, const QString &val ) { DEBUG_BLOCK clearLyrics(); emit newLyricsMessage( key, val ); } void LyricsEngine::clearLyrics() { m_fetching = false; emit fetchingChanged(); m_lyrics.clear(); emit lyricsChanged(); } qreal LyricsEngine::position() const { return (qreal)The::engineController()->trackPosition() * 1000 / The::engineController()->trackLength(); } void LyricsEngine::refetchLyrics() const { Meta::TrackPtr currentTrack = The::engineController()->currentTrack(); if( !currentTrack ) return; - ScriptManager::instance()->notifyFetchLyrics( m_lyrics.artist, m_lyrics.title, "", currentTrack ); -} - -void LyricsEngine::refetchLyrics() -{ - DEBUG_BLOCK - - auto currentTrack = The::engineController()->currentTrack(); - - if( currentTrack ) - ScriptManager::instance()->notifyFetchLyrics( currentTrack->artist()->name(), - currentTrack->name(), "", currentTrack ); - - m_fetching = true; - emit fetchingChanged(); -} - -void LyricsEngine::fetchLyrics(const QString& artist, const QString& title, const QString& url) -{ - DEBUG_BLOCK - - if( !QUrl( url ).isValid() ) - return; - - debug() << "clicked suggestion" << url; - - ScriptManager::instance()->notifyFetchLyrics( artist, title, url, Meta::TrackPtr() ); - - m_fetching = true; - emit fetchingChanged(); + LyricsManager::instance()->loadLyrics( currentTrack, true ); } qreal LyricsEngine::fontSize() const { return Amarok::config( "Context" ).group( "Lyrics" ).readEntry( "fontSize", 18 ); } void LyricsEngine::setFontSize(qreal fontSize) { DEBUG_BLOCK if( fontSize == this->fontSize() ) return; Amarok::config( "Context" ).group( "Lyrics" ).writeEntry( "fontSize", fontSize ); emit fontSizeChanged(); } int LyricsEngine::alignment() const { return Amarok::config( "Context" ).group( "Lyrics" ).readEntry( "alignment", 2 ); } void LyricsEngine::setAlignment(int alignment) { DEBUG_BLOCK if( alignment == this->alignment() ) return; Amarok::config( "Context" ).group( "Lyrics" ).writeEntry( "alignment", alignment ); emit alignmentChanged(); } QString LyricsEngine::font() const { return Amarok::config( "Context" ).group( "Lyrics" ).readEntry( "font", QFont().family() ); } void LyricsEngine::setFont(const QString& font) { DEBUG_BLOCK if( font == this->font() ) return; Amarok::config( "Context" ).group( "Lyrics" ).writeEntry( "font", font ); emit fontChanged(); } QStringList LyricsEngine::availableFonts() const { QStringList list; KFontChooser::getFontList( list, 0 ); return list; } diff --git a/src/context/applets/lyrics/plugin/LyricsEngine.h b/src/context/applets/lyrics/plugin/LyricsEngine.h index 58d519b03f..a408dda75b 100644 --- a/src/context/applets/lyrics/plugin/LyricsEngine.h +++ b/src/context/applets/lyrics/plugin/LyricsEngine.h @@ -1,91 +1,86 @@ /**************************************************************************************** * Copyright (c) 2007 Leo Franchi * * Copyright (c) 2008 Mark Kretschmann * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #ifndef AMAROK_LYRICS_ENGINE #define AMAROK_LYRICS_ENGINE -#include "context/LyricsManager.h" #include "core/meta/Meta.h" #include #include #include -class LyricsEngine : public QObject, public LyricsObserver +class LyricsEngine : public QObject { Q_OBJECT Q_PROPERTY(QString text READ text NOTIFY lyricsChanged) Q_PROPERTY(bool fetching READ fetching NOTIFY fetchingChanged) Q_PROPERTY(QVariantList suggestions READ suggestions NOTIFY lyricsChanged) Q_PROPERTY(qreal position READ position NOTIFY positionChanged) Q_PROPERTY(qreal fontSize READ fontSize WRITE setFontSize NOTIFY fontSizeChanged) Q_PROPERTY(int alignment READ alignment WRITE setAlignment NOTIFY alignmentChanged) Q_PROPERTY(QString font READ font WRITE setFont NOTIFY fontChanged) public: explicit LyricsEngine( QObject* parent = Q_NULLPTR ); - // reimplemented from LyricsObserver - void newLyrics( const LyricsData &lyrics ) Q_DECL_OVERRIDE; - void newSuggestions( const QVariantList &suggest ) Q_DECL_OVERRIDE; - void lyricsMessage( const QString& key, const QString& val ) Q_DECL_OVERRIDE; + void newLyrics( const Meta::TrackPtr &track ); + void newSuggestions( const QVariantList &suggest ); + void lyricsMessage( const QString& key, const QString& val ); - QString text() const { return m_lyrics.text; } + QString text() const { return m_lyrics; } QVariantList suggestions() const { return m_suggestions; } bool fetching() const { return m_fetching; } qreal position() const; qreal fontSize() const; void setFontSize( qreal fontSize ); int alignment() const; void setAlignment( int alignment ); QString font() const; void setFont( const QString &font ); Q_INVOKABLE void refetchLyrics() const; - Q_INVOKABLE void fetchLyrics( const QString &artist, const QString &title, const QString &url ); Q_INVOKABLE QStringList availableFonts() const; Q_SIGNALS: void lyricsChanged(); void newLyricsMessage( const QString& key, const QString &val ); void positionChanged(); void fetchingChanged(); void fontSizeChanged(); void alignmentChanged(); void fontChanged(); private Q_SLOTS: void update(); void onTrackMetadataChanged( Meta::TrackPtr track ); private: - void setLyrics( const LyricsData &lyrics ); void clearLyrics(); - void refetchLyrics(); - LyricsData m_lyrics; + QString m_lyrics; QVariantList m_suggestions; bool m_fetching; bool m_isUpdateInProgress; struct trackMetadata { QString artist; QString title; } m_prevTrackMetadata; }; #endif diff --git a/src/context/applets/lyrics/plugin/LyricsPlugin.cpp b/src/context/applets/lyrics/plugin/LyricsPlugin.cpp index fc64e437fb..d053b4cf32 100644 --- a/src/context/applets/lyrics/plugin/LyricsPlugin.cpp +++ b/src/context/applets/lyrics/plugin/LyricsPlugin.cpp @@ -1,51 +1,51 @@ /* * Copyright 2017 Malte Veerman * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "LyricsEngine.h" #include #include class LyricsPlugin : public QQmlExtensionPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid) public: - void registerTypes(const char* uri) Q_DECL_OVERRIDE + void registerTypes(const char* uri) override { Q_ASSERT(uri == QLatin1String("org.kde.amarok.lyrics")); qmlRegisterSingletonType(uri, 1, 0, "LyricsEngine", lyrics_engine_provider); } static QObject *lyrics_engine_provider(QQmlEngine *engine, QJSEngine *scriptEngine) { Q_UNUSED(engine) Q_UNUSED(scriptEngine) return new LyricsEngine(); } }; #include diff --git a/src/context/applets/photos/plugin/PhotosPlugin.cpp b/src/context/applets/photos/plugin/PhotosPlugin.cpp index aed896e01b..8f0cf0a90f 100644 --- a/src/context/applets/photos/plugin/PhotosPlugin.cpp +++ b/src/context/applets/photos/plugin/PhotosPlugin.cpp @@ -1,51 +1,51 @@ /* * Copyright 2017 Malte Veerman * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "PhotosEngine.h" #include #include class PhotosPlugin : public QQmlExtensionPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid) public: - void registerTypes(const char* uri) Q_DECL_OVERRIDE + void registerTypes(const char* uri) override { Q_ASSERT(uri == QLatin1String("org.kde.amarok.photos")); qmlRegisterSingletonType(uri, 1, 0, "PhotosEngine", photos_engine_provider); } static QObject *photos_engine_provider(QQmlEngine *engine, QJSEngine *scriptEngine) { Q_UNUSED(engine) Q_UNUSED(scriptEngine) return new PhotosEngine(); } }; #include diff --git a/src/context/applets/wikipedia/plugin/WikipediaPlugin.cpp b/src/context/applets/wikipedia/plugin/WikipediaPlugin.cpp index 56ef7f4e84..0f1e09971d 100644 --- a/src/context/applets/wikipedia/plugin/WikipediaPlugin.cpp +++ b/src/context/applets/wikipedia/plugin/WikipediaPlugin.cpp @@ -1,51 +1,51 @@ /* * Copyright 2017 Malte Veerman * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "WikipediaEngine.h" #include #include class WikipediaPlugin : public QQmlExtensionPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid) public: - void registerTypes(const char* uri) Q_DECL_OVERRIDE + void registerTypes(const char* uri) override { Q_ASSERT(uri == QLatin1String("org.kde.amarok.wikipedia")); qmlRegisterSingletonType(uri, 1, 0, "WikipediaEngine", wikipedia_engine_provider); } static QObject *wikipedia_engine_provider(QQmlEngine *engine, QJSEngine *scriptEngine) { Q_UNUSED(engine) Q_UNUSED(scriptEngine) return new WikipediaEngine(); } }; #include diff --git a/src/context/qml_plugin/src/PixmapItem.h b/src/context/qml_plugin/src/PixmapItem.h index 4f1a60fe79..44d35dd956 100644 --- a/src/context/qml_plugin/src/PixmapItem.h +++ b/src/context/qml_plugin/src/PixmapItem.h @@ -1,55 +1,55 @@ /* * Copyright 2018 Malte Veerman * * This program is free software; you can redistribute it and/or * modify it under the 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 PIXMAPITEM_H #define PIXMAPITEM_H #include #include class PixmapItem : public QQuickItem { Q_OBJECT Q_PROPERTY( QPixmap source READ source WRITE setSource NOTIFY sourceChanged RESET resetSource ) Q_PROPERTY( bool valid READ valid NOTIFY sourceChanged ) public: PixmapItem(); void setSource( const QPixmap &source ); void resetSource() { setSource( QPixmap() ); } QPixmap source() const { return m_source; } bool valid() const { return !m_source.isNull(); } - QSGNode* updatePaintNode( QSGNode * oldNode, UpdatePaintNodeData * updatePaintNodeData ) Q_DECL_OVERRIDE; - void geometryChanged( const QRectF &newGeometry, const QRectF &oldGeometry ) Q_DECL_OVERRIDE; + QSGNode* updatePaintNode( QSGNode * oldNode, UpdatePaintNodeData * updatePaintNodeData ) override; + void geometryChanged( const QRectF &newGeometry, const QRectF &oldGeometry ) override; signals: void sourceChanged(); private: QPixmap m_source; bool m_pixmapChanged; bool m_sizeChanged; }; #endif // PIXMAPITEM_H diff --git a/src/context/qml_plugin/src/Plugin.cpp b/src/context/qml_plugin/src/Plugin.cpp index ea8da17cfc..e3f5ba0b7c 100644 --- a/src/context/qml_plugin/src/Plugin.cpp +++ b/src/context/qml_plugin/src/Plugin.cpp @@ -1,45 +1,45 @@ /* * Copyright 2017 Malte Veerman * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "RatingItem.h" #include "PixmapItem.h" #include #include class Plugin : public QQmlExtensionPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid) public: - void registerTypes(const char* uri) Q_DECL_OVERRIDE + void registerTypes(const char* uri) override { Q_ASSERT(uri == QLatin1String("org.kde.amarok.qml")); qmlRegisterType(uri, 1, 0, "RatingItem"); qmlRegisterType(uri, 1, 0, "PixmapItem"); } }; #include diff --git a/src/context/qml_plugin/src/RatingItem.h b/src/context/qml_plugin/src/RatingItem.h index 7ea0f0744c..ad3a5e941c 100644 --- a/src/context/qml_plugin/src/RatingItem.h +++ b/src/context/qml_plugin/src/RatingItem.h @@ -1,158 +1,158 @@ /**************************************************************************************** * Copyright (c) 2008 William Viana Soares * * Copyright (c) 2010 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_RATING_ITEM_H #define AMAROK_RATING_ITEM_H #include class RatingItem : public QQuickPaintedItem { Q_OBJECT Q_PROPERTY( int rating READ rating WRITE setRating NOTIFY ratingChanged ) Q_PROPERTY( int hoverRating READ hoverRating NOTIFY hoverRatingChanged ) Q_PROPERTY( int maxRating READ maxRating WRITE setMaxRating NOTIFY maxRatingChanged ) Q_PROPERTY( int spacing READ spacing WRITE setSpacing NOTIFY spacingChanged ) Q_PROPERTY( Qt::Alignment alignment READ alignment WRITE setAlignment NOTIFY alignmentChanged ) Q_PROPERTY( Qt::LayoutDirection layoutDirection READ layoutDirection WRITE setLayoutDirection NOTIFY layoutDirectionChanged ) Q_PROPERTY( bool halfStepsEnabled READ halfStepsEnabled WRITE setHalfStepsEnabled NOTIFY halfStepsEnabledChanged ) Q_PROPERTY( QString icon READ icon WRITE setIcon NOTIFY iconChanged ) public: /** * Creates a new rating widget. */ explicit RatingItem( QQuickItem* parent = Q_NULLPTR ); /** * Destructor */ ~RatingItem(); /** * @return The current rating. */ unsigned int rating() const; /** * @return the maximum possible rating. */ int maxRating() const; /** * @return the rating that corresponds to the cursor position while hovering. * Returns -1 if the cursor is outside of the item. */ int hoverRating() const; /** * The alignment of the stars. */ Qt::Alignment alignment() const; /** * The layout direction. If RTL the stars * representing the rating value will be drawn from the * right. */ Qt::LayoutDirection layoutDirection() const; /** * The spacing between the rating stars. */ int spacing() const; /** * If half steps are enabled one star equals to 2 rating * points and uneven rating values result in half-stars being * drawn. */ bool halfStepsEnabled() const; /** * The icon name used to draw a star. */ QString icon() const; Q_SIGNALS: void ratingChanged(); void hoverRatingChanged(); void maxRatingChanged(); void spacingChanged(); void layoutDirectionChanged(); void alignmentChanged(); void halfStepsEnabledChanged(); void iconChanged(); void clicked( int newRating ); public Q_SLOTS: /** * Set the current rating. */ void setRating( int rating ); /** * Set the maximum allowed rating value. The default is 10 which means * that a rating from 1 to 10 is selectable. If \a max is uneven steps * are automatically only allowed full. */ void setMaxRating( int max ); /** * If half steps are enabled (the default) then * one rating step corresponds to half a star. */ void setHalfStepsEnabled( bool enabled ); /** * Set the spacing between the pixmaps. The default is 0. */ void setSpacing( int ); /** * The alignment of the stars in the drawing rect. * All alignment flags are supported. */ void setAlignment( Qt::Alignment align ); /** * LTR or RTL */ void setLayoutDirection( Qt::LayoutDirection direction ); /** * Set a custom icon. Defaults to "rating". */ void setIcon( const QString& iconName ); protected: - virtual void mousePressEvent( QMouseEvent* e ) Q_DECL_OVERRIDE; - virtual void hoverMoveEvent( QHoverEvent* e ) Q_DECL_OVERRIDE; - virtual void hoverEnterEvent( QHoverEvent* e ) Q_DECL_OVERRIDE; - virtual void hoverLeaveEvent( QHoverEvent* e ) Q_DECL_OVERRIDE; - virtual void paint( QPainter* painter ) Q_DECL_OVERRIDE; + virtual void mousePressEvent( QMouseEvent* e ) override; + virtual void hoverMoveEvent( QHoverEvent* e ) override; + virtual void hoverEnterEvent( QHoverEvent* e ) override; + virtual void hoverLeaveEvent( QHoverEvent* e ) override; + virtual void paint( QPainter* painter ) override; private: class Private; Private* const d; }; #endif diff --git a/src/core-impl/collections/daap/daapreader/Reader.h b/src/core-impl/collections/daap/daapreader/Reader.h index 87d4efeb47..01f7c4479e 100644 --- a/src/core-impl/collections/daap/daapreader/Reader.h +++ b/src/core-impl/collections/daap/daapreader/Reader.h @@ -1,153 +1,153 @@ /**************************************************************************************** * Copyright (c) 2006 Ian Monroe * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) version 3 or * * any later version accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #ifndef DAAPREADER_H #define DAAPREADER_H #include #include #include #include class QString; namespace Collections { class DaapCollection; } class QHttpResponseHeader; namespace Daap { typedef QMap Map; enum ContentTypes { INVALID = 0, CHAR = 1, SHORT = 3, LONG = 5, LONGLONG = 7, STRING = 9, DATE = 10, DVERSION = 11, CONTAINER = 12 }; struct Code { Code() : type(INVALID) { } Code( const QString& nName, ContentTypes nType ) : name( nName ), type( nType ) { } ~Code() { } QString name; ContentTypes type; }; /** The nucleus of the DAAP client; composes queries and parses responses. @author Ian Monroe */ class Reader : public QObject { Q_OBJECT public: Reader( Collections::DaapCollection *mc, const QString& host, quint16 port, const QString& password, QObject* parent, const char* name ); ~Reader(); //QPtrList getSongList(); enum Options { SESSION_ID = 1, SERVER_VERSION = 2 }; void loginRequest(); void logoutRequest(); int sessionId() const { return m_sessionId; } QString host() const { return m_host; } quint16 port() const { return m_port; } bool parseSongList( const QByteArray &data, bool set_collection = false); public Q_SLOTS: void logoutRequestFinished(); void contentCodesReceived(); void loginHeaderReceived(); void loginFinished(); void updateFinished(); void databaseIdFinished(); void songListFinished(); void fetchingError( const QString& error ); Q_SIGNALS: //void daapBundles( const QString& host, Daap::SongList bundles ); void httpError( const QString& ); void passwordRequired(); private: /** * Make a map-vector tree out of the DAAP binary result * @param raw stream of DAAP reply */ Map parse( QDataStream &raw); static void addElement( Map &parentMap, char* tag, QVariant element ); //!< supporter function for parse static quint32 getTagAndLength( QDataStream &raw, char tag[5] ); QVariant readTagData(QDataStream &, char[5], quint32); void addTrack( const QString& itemId, const QString& title, const QString& artist, const QString& composer, const QString& commment, const QString& album, const QString& genre, int year, const QString& format, qint32 trackNumber, qint32 songTime ); QMap m_codes; Collections::DaapCollection *m_memColl; QString m_host; quint16 m_port; QString m_loginString; QString m_databaseId; int m_sessionId; QString m_password; TrackMap m_trackMap; ArtistMap m_artistMap; AlbumMap m_albumMap; GenreMap m_genreMap; ComposerMap m_composerMap; YearMap m_yearMap; }; class WorkerThread : public QObject, public ThreadWeaver::Job { Q_OBJECT public: WorkerThread( const QByteArray &data, Reader* reader, Collections::DaapCollection *coll ); virtual ~WorkerThread(); - virtual bool success() const Q_DECL_OVERRIDE; + virtual bool success() const override; protected: - void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void run(ThreadWeaver::JobPointer self=QSharedPointer(), ThreadWeaver::Thread *thread = 0) Q_DECL_OVERRIDE; + void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void run(ThreadWeaver::JobPointer self=QSharedPointer(), ThreadWeaver::Thread *thread = 0) override; 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); private: bool m_success; QByteArray m_data; Reader *m_reader; }; } #endif diff --git a/src/core-impl/collections/db/sql/SqlMeta.cpp b/src/core-impl/collections/db/sql/SqlMeta.cpp index 7ace034eae..8ce22b9af3 100644 --- a/src/core-impl/collections/db/sql/SqlMeta.cpp +++ b/src/core-impl/collections/db/sql/SqlMeta.cpp @@ -1,2252 +1,2270 @@ /**************************************************************************************** * Copyright (c) 2007 Maximilian Kossick * * Copyright (c) 2008 Daniel Winter * * Copyright (c) 2010 Ralf Engels * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #define DEBUG_PREFIX "SqlMeta" #include "SqlMeta.h" #include "amarokconfig.h" #include "SqlCapabilities.h" #include "SqlCollection.h" #include "SqlQueryMaker.h" #include "SqlRegistry.h" #include "SqlReadLabelCapability.h" #include "SqlWriteLabelCapability.h" #include "MetaTagLib.h" // for getting an embedded cover #include "amarokurls/BookmarkMetaActions.h" #include #include "core/meta/support/MetaUtility.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include "core/capabilities/BookmarkThisCapability.h" #include "core-impl/capabilities/AlbumActionsCapability.h" #include "core-impl/collections/db/MountPointManager.h" #include "core-impl/collections/support/ArtistHelper.h" #include "core-impl/collections/support/jobs/WriteTagsJob.h" #include "covermanager/CoverCache.h" #include "covermanager/CoverFetcher.h" #include #include #include #include #include #include #include #include #include #include #include #include #include -#include - // additional constants namespace Meta { static const qint64 valAlbumId = valCustom + 1; } using namespace Meta; QString SqlTrack::getTrackReturnValues() { //do not use any weird column names that contains commas: this will break getTrackReturnValuesCount() // NOTE: when changing this, always check that SqlTrack::TrackReturnIndex enum remains valid return "urls.id, urls.deviceid, urls.rpath, urls.directory, urls.uniqueid, " "tracks.id, tracks.title, tracks.comment, " "tracks.tracknumber, tracks.discnumber, " "statistics.score, statistics.rating, " "tracks.bitrate, tracks.length, " "tracks.filesize, tracks.samplerate, " "statistics.id, " "statistics.createdate, statistics.accessdate, " "statistics.playcount, tracks.filetype, tracks.bpm, " "tracks.createdate, tracks.modifydate, tracks.albumgain, tracks.albumpeakgain, " "tracks.trackgain, tracks.trackpeakgain, " "artists.name, artists.id, " // TODO: just reading the id should be sufficient "albums.name, albums.id, albums.artist, " // TODO: again here "genres.name, genres.id, " // TODO: again here "composers.name, composers.id, " // TODO: again here "years.name, years.id"; // TODO: again here } QString SqlTrack::getTrackJoinConditions() { return "LEFT JOIN tracks ON urls.id = tracks.url " "LEFT JOIN statistics ON urls.id = statistics.url " "LEFT JOIN artists ON tracks.artist = artists.id " "LEFT JOIN albums ON tracks.album = albums.id " "LEFT JOIN genres ON tracks.genre = genres.id " "LEFT JOIN composers ON tracks.composer = composers.id " "LEFT JOIN years ON tracks.year = years.id"; } int SqlTrack::getTrackReturnValueCount() { static int count = getTrackReturnValues().split( ',' ).count(); return count; } SqlTrack::SqlTrack( Collections::SqlCollection *collection, int deviceId, const QString &rpath, int directoryId, const QString &uidUrl ) : Track() , m_collection( collection ) , m_batchUpdate( 0 ) , m_writeFile( true ) , m_labelsInCache( false ) { m_batchUpdate = 1; // I don't want commits yet m_urlId = -1; // this will be set with the first database write m_trackId = -1; // this will be set with the first database write m_statisticsId = -1; setUrl( deviceId, rpath, directoryId ); m_url = QUrl::fromUserInput(m_cache.value( Meta::valUrl ).toString()); // SqlRegistry already has this url setUidUrl( uidUrl ); m_uid = m_cache.value( Meta::valUniqueId ).toString(); // SqlRegistry already has this uid // ensure that these values get a correct database id m_cache.insert( Meta::valAlbum, QVariant() ); m_cache.insert( Meta::valArtist, QVariant() ); m_cache.insert( Meta::valComposer, QVariant() ); m_cache.insert( Meta::valYear, QVariant() ); m_cache.insert( Meta::valGenre, QVariant() ); m_trackNumber = 0; m_discNumber = 0; m_score = 0; m_rating = 0; m_bitrate = 0; m_length = 0; m_filesize = 0; m_sampleRate = 0; m_playCount = 0; m_bpm = 0.0; m_createDate = QDateTime::currentDateTime(); m_cache.insert( Meta::valCreateDate, m_createDate ); // ensure that the created date is written the next time m_trackGain = 0.0; m_trackPeakGain = 0.0; m_albumGain = 0.0; m_albumPeakGain = 0.0; m_batchUpdate = 0; // reset in-batch-update without committing m_filetype = Amarok::Unknown; } SqlTrack::SqlTrack( Collections::SqlCollection *collection, const QStringList &result ) : Track() , m_collection( collection ) , m_batchUpdate( 0 ) , m_writeFile( true ) , m_labelsInCache( false ) { QStringList::ConstIterator iter = result.constBegin(); m_urlId = (*(iter++)).toInt(); Q_ASSERT( m_urlId > 0 && "refusing to create SqlTrack with non-positive urlId, please file a bug" ); m_deviceId = (*(iter++)).toInt(); Q_ASSERT( m_deviceId != 0 && "refusing to create SqlTrack with zero deviceId, please file a bug" ); m_rpath = *(iter++); m_directoryId = (*(iter++)).toInt(); Q_ASSERT( m_directoryId > 0 && "refusing to create SqlTrack with non-positive directoryId, please file a bug" ); m_url = QUrl::fromLocalFile( m_collection->mountPointManager()->getAbsolutePath( m_deviceId, m_rpath ) ); m_uid = *(iter++); m_trackId = (*(iter++)).toInt(); m_title = *(iter++); m_comment = *(iter++); m_trackNumber = (*(iter++)).toInt(); m_discNumber = (*(iter++)).toInt(); m_score = (*(iter++)).toDouble(); m_rating = (*(iter++)).toInt(); m_bitrate = (*(iter++)).toInt(); m_length = (*(iter++)).toInt(); m_filesize = (*(iter++)).toInt(); m_sampleRate = (*(iter++)).toInt(); m_statisticsId = (*(iter++)).toInt(); uint time = (*(iter++)).toUInt(); if( time > 0 ) m_firstPlayed = QDateTime::fromTime_t(time); time = (*(iter++)).toUInt(); if( time > 0 ) m_lastPlayed = QDateTime::fromTime_t(time); m_playCount = (*(iter++)).toInt(); m_filetype = Amarok::FileType( (*(iter++)).toInt() ); m_bpm = (*(iter++)).toFloat(); m_createDate = QDateTime::fromTime_t((*(iter++)).toUInt()); m_modifyDate = QDateTime::fromTime_t((*(iter++)).toUInt()); // if there is no track gain, we assume a gain of 0 // if there is no album gain, we use the track gain QString albumGain = *(iter++); QString albumPeakGain = *(iter++); m_trackGain = (*(iter++)).toDouble(); m_trackPeakGain = (*(iter++)).toDouble(); if ( albumGain.isEmpty() ) { m_albumGain = m_trackGain; m_albumPeakGain = m_trackPeakGain; } else { m_albumGain = albumGain.toDouble(); m_albumPeakGain = albumPeakGain.toDouble(); } SqlRegistry* registry = m_collection->registry(); QString artist = *(iter++); int artistId = (*(iter++)).toInt(); if( artistId > 0 ) m_artist = registry->getArtist( artistId, artist ); QString album = *(iter++); int albumId =(*(iter++)).toInt(); int albumArtistId = (*(iter++)).toInt(); if( albumId > 0 ) // sanity check m_album = registry->getAlbum( albumId, album, albumArtistId ); QString genre = *(iter++); int genreId = (*(iter++)).toInt(); if( genreId > 0 ) // sanity check m_genre = registry->getGenre( genreId, genre ); QString composer = *(iter++); int composerId = (*(iter++)).toInt(); if( composerId > 0 ) // sanity check m_composer = registry->getComposer( composerId, composer ); QString year = *(iter++); int yearId = (*(iter++)).toInt(); if( yearId > 0 ) // sanity check m_year = registry->getYear( year.toInt(), yearId ); //Q_ASSERT_X( iter == result.constEnd(), "SqlTrack( Collections::SqlCollection*, QStringList )", "number of expected fields did not match number of actual fields: expected " + result.size() ); } SqlTrack::~SqlTrack() { QWriteLocker locker( &m_lock ); if( !m_cache.isEmpty() ) warning() << "Destroying track with unwritten meta information." << m_title << "cache:" << m_cache; if( m_batchUpdate ) warning() << "Destroying track with unclosed batch update." << m_title; } QString SqlTrack::name() const { QReadLocker locker( &m_lock ); return m_title; } QString SqlTrack::prettyName() const { if ( !name().isEmpty() ) return name(); return prettyTitle( m_url.fileName() ); } void SqlTrack::setTitle( const QString &newTitle ) { QWriteLocker locker( &m_lock ); if ( m_title != newTitle ) commitIfInNonBatchUpdate( Meta::valTitle, newTitle ); } QUrl SqlTrack::playableUrl() const { QReadLocker locker( &m_lock ); return m_url; } QString SqlTrack::prettyUrl() const { QReadLocker locker( &m_lock ); return m_url.path(); } void SqlTrack::setUrl( int deviceId, const QString &rpath, int directoryId ) { QWriteLocker locker( &m_lock ); if( m_deviceId == deviceId && m_rpath == rpath && m_directoryId == directoryId ) return; m_deviceId = deviceId; m_rpath = rpath; m_directoryId = directoryId; commitIfInNonBatchUpdate( Meta::valUrl, m_collection->mountPointManager()->getAbsolutePath( m_deviceId, m_rpath ) ); } QString SqlTrack::uidUrl() const { QReadLocker locker( &m_lock ); return m_uid; } void SqlTrack::setUidUrl( const QString &uid ) { QWriteLocker locker( &m_lock ); // -- ensure that the uid starts with the collections protocol (amarok-sqltrackuid) QString newid = uid; QString protocol; if( m_collection ) protocol = m_collection->uidUrlProtocol()+"://"; if( !newid.startsWith( protocol ) ) newid.prepend( protocol ); m_cache.insert( Meta::valUniqueId, newid ); if( m_batchUpdate == 0 ) { debug() << "setting uidUrl manually...did you really mean to do this?"; commitIfInNonBatchUpdate(); } } QString SqlTrack::notPlayableReason() const { return localFileNotPlayableReason( playableUrl().toLocalFile() ); } bool SqlTrack::isEditable() const { QReadLocker locker( &m_lock ); QFile::Permissions p = QFile::permissions( m_url.path() ); const bool editable = ( p & QFile::WriteUser ) || ( p & QFile::WriteGroup ) || ( p & QFile::WriteOther ); return m_collection && QFile::exists( m_url.path() ) && editable; } Meta::AlbumPtr SqlTrack::album() const { QReadLocker locker( &m_lock ); return m_album; } void SqlTrack::setAlbum( const QString &newAlbum ) { QWriteLocker locker( &m_lock ); if( !m_album || m_album->name() != newAlbum ) commitIfInNonBatchUpdate( Meta::valAlbum, newAlbum ); } void SqlTrack::setAlbum( int albumId ) { QWriteLocker locker( &m_lock ); commitIfInNonBatchUpdate( Meta::valAlbumId, albumId ); } Meta::ArtistPtr SqlTrack::artist() const { QReadLocker locker( &m_lock ); return m_artist; } void SqlTrack::setArtist( const QString &newArtist ) { QWriteLocker locker( &m_lock ); if( !m_artist || m_artist->name() != newArtist ) commitIfInNonBatchUpdate( Meta::valArtist, newArtist ); } void SqlTrack::setAlbumArtist( const QString &newAlbumArtist ) { if( m_album.isNull() ) return; if( !newAlbumArtist.compare( "Various Artists", Qt::CaseInsensitive ) || !newAlbumArtist.compare( i18n( "Various Artists" ), Qt::CaseInsensitive ) ) { commitIfInNonBatchUpdate( Meta::valCompilation, true ); } else { m_cache.insert( Meta::valAlbumArtist, ArtistHelper::realTrackArtist( newAlbumArtist ) ); m_cache.insert( Meta::valCompilation, false ); commitIfInNonBatchUpdate(); } } Meta::ComposerPtr SqlTrack::composer() const { QReadLocker locker( &m_lock ); return m_composer; } void SqlTrack::setComposer( const QString &newComposer ) { QWriteLocker locker( &m_lock ); if( !m_composer || m_composer->name() != newComposer ) commitIfInNonBatchUpdate( Meta::valComposer, newComposer ); } Meta::YearPtr SqlTrack::year() const { QReadLocker locker( &m_lock ); return m_year; } void SqlTrack::setYear( int newYear ) { QWriteLocker locker( &m_lock ); if( !m_year || m_year->year() != newYear ) commitIfInNonBatchUpdate( Meta::valYear, newYear ); } Meta::GenrePtr SqlTrack::genre() const { QReadLocker locker( &m_lock ); return m_genre; } void SqlTrack::setGenre( const QString &newGenre ) { QWriteLocker locker( &m_lock ); if( !m_genre || m_genre->name() != newGenre ) commitIfInNonBatchUpdate( Meta::valGenre, newGenre ); } QString SqlTrack::type() const { QReadLocker locker( &m_lock ); return m_url.isLocalFile() ? Amarok::FileTypeSupport::toString( m_filetype ) // don't localize. This is used in different files to identify streams, see EngineController quirks : "stream"; } void SqlTrack::setType( Amarok::FileType newType ) { QWriteLocker locker( &m_lock ); if ( m_filetype != newType ) commitIfInNonBatchUpdate( Meta::valFormat, int(newType) ); } qreal SqlTrack::bpm() const { QReadLocker locker( &m_lock ); return m_bpm; } void SqlTrack::setBpm( const qreal newBpm ) { QWriteLocker locker( &m_lock ); if ( m_bpm != newBpm ) commitIfInNonBatchUpdate( Meta::valBpm, newBpm ); } QString SqlTrack::comment() const { QReadLocker locker( &m_lock ); return m_comment; } void SqlTrack::setComment( const QString &newComment ) { QWriteLocker locker( &m_lock ); if( newComment != m_comment ) commitIfInNonBatchUpdate( Meta::valComment, newComment ); } double SqlTrack::score() const { QReadLocker locker( &m_lock ); return m_score; } void SqlTrack::setScore( double newScore ) { QWriteLocker locker( &m_lock ); newScore = qBound( double(0), newScore, double(100) ); if( qAbs( newScore - m_score ) > 0.001 ) // we don't commit for minimal changes commitIfInNonBatchUpdate( Meta::valScore, newScore ); } int SqlTrack::rating() const { QReadLocker locker( &m_lock ); return m_rating; } void SqlTrack::setRating( int newRating ) { QWriteLocker locker( &m_lock ); newRating = qBound( 0, newRating, 10 ); if( newRating != m_rating ) commitIfInNonBatchUpdate( Meta::valRating, newRating ); } qint64 SqlTrack::length() const { QReadLocker locker( &m_lock ); return m_length; } void SqlTrack::setLength( qint64 newLength ) { QWriteLocker locker( &m_lock ); if( newLength != m_length ) commitIfInNonBatchUpdate( Meta::valLength, newLength ); } int SqlTrack::filesize() const { QReadLocker locker( &m_lock ); return m_filesize; } int SqlTrack::sampleRate() const { QReadLocker locker( &m_lock ); return m_sampleRate; } void SqlTrack::setSampleRate( int newSampleRate ) { QWriteLocker locker( &m_lock ); if( newSampleRate != m_sampleRate ) commitIfInNonBatchUpdate( Meta::valSamplerate, newSampleRate ); } int SqlTrack::bitrate() const { QReadLocker locker( &m_lock ); return m_bitrate; } void SqlTrack::setBitrate( int newBitrate ) { QWriteLocker locker( &m_lock ); if( newBitrate != m_bitrate ) commitIfInNonBatchUpdate( Meta::valBitrate, newBitrate ); } QDateTime SqlTrack::createDate() const { QReadLocker locker( &m_lock ); return m_createDate; } QDateTime SqlTrack::modifyDate() const { QReadLocker locker( &m_lock ); return m_modifyDate; } void SqlTrack::setModifyDate( const QDateTime &newTime ) { QWriteLocker locker( &m_lock ); if( newTime != m_modifyDate ) commitIfInNonBatchUpdate( Meta::valModified, newTime ); } int SqlTrack::trackNumber() const { QReadLocker locker( &m_lock ); return m_trackNumber; } void SqlTrack::setTrackNumber( int newTrackNumber ) { QWriteLocker locker( &m_lock ); if( newTrackNumber != m_trackNumber ) commitIfInNonBatchUpdate( Meta::valTrackNr, newTrackNumber ); } int SqlTrack::discNumber() const { QReadLocker locker( &m_lock ); return m_discNumber; } void SqlTrack::setDiscNumber( int newDiscNumber ) { QWriteLocker locker( &m_lock ); if( newDiscNumber != m_discNumber ) commitIfInNonBatchUpdate( Meta::valDiscNr, newDiscNumber ); } QDateTime SqlTrack::lastPlayed() const { QReadLocker locker( &m_lock ); return m_lastPlayed; } void SqlTrack::setLastPlayed( const QDateTime &newTime ) { QWriteLocker locker( &m_lock ); if( newTime != m_lastPlayed ) commitIfInNonBatchUpdate( Meta::valLastPlayed, newTime ); } QDateTime SqlTrack::firstPlayed() const { QReadLocker locker( &m_lock ); return m_firstPlayed; } void SqlTrack::setFirstPlayed( const QDateTime &newTime ) { QWriteLocker locker( &m_lock ); if( newTime != m_firstPlayed ) commitIfInNonBatchUpdate( Meta::valFirstPlayed, newTime ); } int SqlTrack::playCount() const { QReadLocker locker( &m_lock ); return m_playCount; } void SqlTrack::setPlayCount( const int newCount ) { QWriteLocker locker( &m_lock ); if( newCount != m_playCount ) commitIfInNonBatchUpdate( Meta::valPlaycount, newCount ); } qreal SqlTrack::replayGain( ReplayGainTag mode ) const { QReadLocker locker(&(const_cast(this)->m_lock)); switch( mode ) { case Meta::ReplayGain_Track_Gain: return m_trackGain; case Meta::ReplayGain_Track_Peak: return m_trackPeakGain; case Meta::ReplayGain_Album_Gain: return m_albumGain; case Meta::ReplayGain_Album_Peak: return m_albumPeakGain; } return 0.0; } void SqlTrack::setReplayGain( Meta::ReplayGainTag mode, qreal value ) { if( qAbs( value - replayGain( mode ) ) < 0.01 ) return; { QWriteLocker locker( &m_lock ); switch( mode ) { case Meta::ReplayGain_Track_Gain: m_cache.insert( Meta::valTrackGain, value ); break; case Meta::ReplayGain_Track_Peak: m_cache.insert( Meta::valTrackGainPeak, value ); break; case Meta::ReplayGain_Album_Gain: m_cache.insert( Meta::valAlbumGain, value ); break; case Meta::ReplayGain_Album_Peak: m_cache.insert( Meta::valAlbumGainPeak, value ); break; } commitIfInNonBatchUpdate(); } } void SqlTrack::beginUpdate() { QWriteLocker locker( &m_lock ); m_batchUpdate++; } void SqlTrack::endUpdate() { QWriteLocker locker( &m_lock ); Q_ASSERT( m_batchUpdate > 0 ); m_batchUpdate--; commitIfInNonBatchUpdate(); } void SqlTrack::commitIfInNonBatchUpdate( qint64 field, const QVariant &value ) { m_cache.insert( field, value ); commitIfInNonBatchUpdate(); } void SqlTrack::commitIfInNonBatchUpdate() { if( m_batchUpdate > 0 || m_cache.isEmpty() ) return; // nothing to do // debug() << "SqlTrack::commitMetaDataChanges " << m_cache; QString oldUid = m_uid; // for all the following objects we need to invalidate the cache and // notify the observers after the update AmarokSharedPointer oldArtist; AmarokSharedPointer newArtist; AmarokSharedPointer oldAlbum; AmarokSharedPointer newAlbum; AmarokSharedPointer oldComposer; AmarokSharedPointer newComposer; AmarokSharedPointer oldGenre; AmarokSharedPointer newGenre; AmarokSharedPointer oldYear; AmarokSharedPointer newYear; if( m_cache.contains( Meta::valFormat ) ) m_filetype = Amarok::FileType(m_cache.value( Meta::valFormat ).toInt()); if( m_cache.contains( Meta::valTitle ) ) m_title = m_cache.value( Meta::valTitle ).toString(); if( m_cache.contains( Meta::valComment ) ) m_comment = m_cache.value( Meta::valComment ).toString(); if( m_cache.contains( Meta::valScore ) ) m_score = m_cache.value( Meta::valScore ).toDouble(); if( m_cache.contains( Meta::valRating ) ) m_rating = m_cache.value( Meta::valRating ).toInt(); if( m_cache.contains( Meta::valLength ) ) m_length = m_cache.value( Meta::valLength ).toLongLong(); if( m_cache.contains( Meta::valSamplerate ) ) m_sampleRate = m_cache.value( Meta::valSamplerate ).toInt(); if( m_cache.contains( Meta::valBitrate ) ) m_bitrate = m_cache.value( Meta::valBitrate ).toInt(); if( m_cache.contains( Meta::valFirstPlayed ) ) m_firstPlayed = m_cache.value( Meta::valFirstPlayed ).toDateTime(); if( m_cache.contains( Meta::valLastPlayed ) ) m_lastPlayed = m_cache.value( Meta::valLastPlayed ).toDateTime(); if( m_cache.contains( Meta::valTrackNr ) ) m_trackNumber = m_cache.value( Meta::valTrackNr ).toInt(); if( m_cache.contains( Meta::valDiscNr ) ) m_discNumber = m_cache.value( Meta::valDiscNr ).toInt(); if( m_cache.contains( Meta::valPlaycount ) ) m_playCount = m_cache.value( Meta::valPlaycount ).toInt(); if( m_cache.contains( Meta::valCreateDate ) ) m_createDate = m_cache.value( Meta::valCreateDate ).toDateTime(); if( m_cache.contains( Meta::valModified ) ) m_modifyDate = m_cache.value( Meta::valModified ).toDateTime(); if( m_cache.contains( Meta::valTrackGain ) ) m_trackGain = m_cache.value( Meta::valTrackGain ).toDouble(); if( m_cache.contains( Meta::valTrackGainPeak ) ) m_trackPeakGain = m_cache.value( Meta::valTrackGainPeak ).toDouble(); if( m_cache.contains( Meta::valAlbumGain ) ) m_albumGain = m_cache.value( Meta::valAlbumGain ).toDouble(); if( m_cache.contains( Meta::valAlbumGainPeak ) ) m_albumPeakGain = m_cache.value( Meta::valAlbumGainPeak ).toDouble(); if( m_cache.contains( Meta::valUrl ) ) { // slight problem here: it is possible to set the url to the one of an already // existing track, which is forbidden by the database // At least the ScanResultProcessor handles this problem QUrl oldUrl = m_url; QUrl newUrl = QUrl::fromUserInput(m_cache.value( Meta::valUrl ).toString()); if( oldUrl != newUrl ) m_collection->registry()->updateCachedUrl( oldUrl.path(), newUrl.path() ); m_url = newUrl; // debug() << "m_cache contains a new URL, setting m_url to " << m_url << " from " << oldUrl; } if( m_cache.contains( Meta::valArtist ) ) { //invalidate cache of the old artist... oldArtist = static_cast(m_artist.data()); m_artist = m_collection->registry()->getArtist( m_cache.value( Meta::valArtist ).toString() ); //and the new one newArtist = static_cast(m_artist.data()); // if the current album is no compilation and we aren't changing // the album anyway, then we need to create a new album with the // new artist. if( m_album ) { bool supp = m_album->suppressImageAutoFetch(); m_album->setSuppressImageAutoFetch( true ); if( m_album->hasAlbumArtist() && m_album->albumArtist() == oldArtist && !m_cache.contains( Meta::valAlbum ) && !m_cache.contains( Meta::valAlbumId ) ) { m_cache.insert( Meta::valAlbum, m_album->name() ); } m_album->setSuppressImageAutoFetch( supp ); } } if( m_cache.contains( Meta::valAlbum ) || m_cache.contains( Meta::valAlbumId ) || m_cache.contains( Meta::valAlbumArtist ) ) { oldAlbum = static_cast(m_album.data()); if( m_cache.contains( Meta::valAlbumId ) ) m_album = m_collection->registry()->getAlbum( m_cache.value( Meta::valAlbumId ).toInt() ); else { // the album should remain a compilation after renaming it // TODO: we would need to use the artist helper QString newArtistName; if( m_cache.contains( Meta::valAlbumArtist ) ) newArtistName = m_cache.value( Meta::valAlbumArtist ).toString(); else if( oldAlbum && oldAlbum->isCompilation() && !oldAlbum->name().isEmpty() ) newArtistName.clear(); else if( oldAlbum && oldAlbum->hasAlbumArtist() ) newArtistName = oldAlbum->albumArtist()->name(); m_album = m_collection->registry()->getAlbum( m_cache.contains( Meta::valAlbum) ? m_cache.value( Meta::valAlbum ).toString() : oldAlbum->name(), newArtistName ); } newAlbum = static_cast(m_album.data()); // due to the complex logic with artist and albumId it can happen that // in the end we have the same album as before. if( newAlbum == oldAlbum ) { m_cache.remove( Meta::valAlbum ); m_cache.remove( Meta::valAlbumId ); m_cache.remove( Meta::valAlbumArtist ); oldAlbum.clear(); newAlbum.clear(); } } if( m_cache.contains( Meta::valComposer ) ) { oldComposer = static_cast(m_composer.data()); m_composer = m_collection->registry()->getComposer( m_cache.value( Meta::valComposer ).toString() ); newComposer = static_cast(m_composer.data()); } if( m_cache.contains( Meta::valGenre ) ) { oldGenre = static_cast(m_genre.data()); m_genre = m_collection->registry()->getGenre( m_cache.value( Meta::valGenre ).toString() ); newGenre = static_cast(m_genre.data()); } if( m_cache.contains( Meta::valYear ) ) { oldYear = static_cast(m_year.data()); m_year = m_collection->registry()->getYear( m_cache.value( Meta::valYear ).toInt() ); newYear = static_cast(m_year.data()); } if( m_cache.contains( Meta::valBpm ) ) m_bpm = m_cache.value( Meta::valBpm ).toDouble(); // --- write the file if( m_writeFile && AmarokConfig::writeBack() ) { Meta::Tag::writeTags( m_url.path(), m_cache, AmarokConfig::writeBackStatistics() ); // unique id may have changed QString uid = Meta::Tag::readTags( m_url.path() ).value( Meta::valUniqueId ).toString(); if( !uid.isEmpty() ) m_cache[ Meta::valUniqueId ] = m_collection->generateUidUrl( uid ); } // needs to be after writing to file; that may have changed generated uid if( m_cache.contains( Meta::valUniqueId ) ) { QString newUid = m_cache.value( Meta::valUniqueId ).toString(); if( oldUid != newUid && m_collection->registry()->updateCachedUid( oldUid, newUid ) ) m_uid = newUid; } //updating the fields might have changed the filesize //read the current filesize so that we can update the db QFile file( m_url.path() ); if( file.exists() ) { if( m_filesize != file.size() ) { m_cache.insert( Meta::valFilesize, file.size() ); m_filesize = file.size(); } } // --- add to the registry dirty list SqlRegistry *registry = 0; // prevent writing to the db when we don't know the directory, bug 322474. Note that // m_urlId is created by registry->commitDirtyTracks() if there is none. if( m_deviceId != 0 && m_directoryId > 0 ) { registry = m_collection->registry(); QMutexLocker locker2( ®istry->m_blockMutex ); registry->m_dirtyTracks.insert( Meta::SqlTrackPtr( this ) ); if( oldArtist ) registry->m_dirtyArtists.insert( oldArtist ); if( newArtist ) registry->m_dirtyArtists.insert( newArtist ); if( oldAlbum ) registry->m_dirtyAlbums.insert( oldAlbum ); if( newAlbum ) registry->m_dirtyAlbums.insert( newAlbum ); if( oldComposer ) registry->m_dirtyComposers.insert( oldComposer ); if( newComposer ) registry->m_dirtyComposers.insert( newComposer ); if( oldGenre ) registry->m_dirtyGenres.insert( oldGenre ); if( newGenre ) registry->m_dirtyGenres.insert( newGenre ); if( oldYear ) registry->m_dirtyYears.insert( oldYear ); if( newYear ) registry->m_dirtyYears.insert( newYear ); } else error() << Q_FUNC_INFO << "non-positive urlId, zero deviceId or non-positive" << "directoryId encountered in track" << m_url << "urlId:" << m_urlId << "deviceId:" << m_deviceId << "directoryId:" << m_directoryId << "- not writing back metadata" << "changes to the database."; m_lock.unlock(); // or else we provoke a deadlock // copy the image BUG: 203211 (we need to do it here or provoke a dead lock) if( oldAlbum && newAlbum ) { bool oldSupp = oldAlbum->suppressImageAutoFetch(); bool newSupp = newAlbum->suppressImageAutoFetch(); oldAlbum->setSuppressImageAutoFetch( true ); newAlbum->setSuppressImageAutoFetch( true ); if( oldAlbum->hasImage() && !newAlbum->hasImage() ) newAlbum->setImage( oldAlbum->imageLocation().path() ); oldAlbum->setSuppressImageAutoFetch( oldSupp ); newAlbum->setSuppressImageAutoFetch( newSupp ); } if( registry ) registry->commitDirtyTracks(); // calls notifyObservers() as appropriate else notifyObservers(); m_lock.lockForWrite(); // reset back to state it was during call if( m_uid != oldUid ) { updatePlaylistsToDb( m_cache, oldUid ); updateEmbeddedCoversToDb( m_cache, oldUid ); } // --- clean up m_cache.clear(); } void SqlTrack::updatePlaylistsToDb( const FieldHash &fields, const QString &oldUid ) { if( fields.isEmpty() ) return; // nothing to do auto storage = m_collection->sqlStorage(); QStringList tags; // keep this in sync with SqlPlaylist::saveTracks()! if( fields.contains( Meta::valUrl ) ) tags << QString( "url='%1'" ).arg( storage->escape( m_url.path() ) ); if( fields.contains( Meta::valTitle ) ) tags << QString( "title='%1'" ).arg( storage->escape( m_title ) ); if( fields.contains( Meta::valAlbum ) ) tags << QString( "album='%1'" ).arg( m_album ? storage->escape( m_album->prettyName() ) : "" ); if( fields.contains( Meta::valArtist ) ) tags << QString( "artist='%1'" ).arg( m_artist ? storage->escape( m_artist->prettyName() ) : "" ); if( fields.contains( Meta::valLength ) ) tags << QString( "length=%1").arg( QString::number( m_length ) ); if( fields.contains( Meta::valUniqueId ) ) { // SqlPlaylist mirrors uniqueid to url, update it too, bug 312128 tags << QString( "url='%1'" ).arg( storage->escape( m_uid ) ); tags << QString( "uniqueid='%1'" ).arg( storage->escape( m_uid ) ); } if( !tags.isEmpty() ) { QString update = "UPDATE playlist_tracks SET %1 WHERE uniqueid = '%2';"; update = update.arg( tags.join( ", " ), storage->escape( oldUid ) ); storage->query( update ); } } void SqlTrack::updateEmbeddedCoversToDb( const FieldHash &fields, const QString &oldUid ) { if( fields.isEmpty() ) return; // nothing to do auto storage = m_collection->sqlStorage(); QString tags; if( fields.contains( Meta::valUniqueId ) ) tags += QString( ",path='%1'" ).arg( storage->escape( m_uid ) ); if( !tags.isEmpty() ) { tags = tags.remove(0, 1); // the first character is always a ',' QString update = "UPDATE images SET %1 WHERE path = '%2';"; update = update.arg( tags, storage->escape( oldUid ) ); storage->query( update ); } } QString SqlTrack::prettyTitle( const QString &filename ) //static { QString s = filename; //just so the code is more readable //remove .part extension if it exists if (s.endsWith( ".part" )) s = s.left( s.length() - 5 ); //remove file extension, s/_/ /g and decode %2f-like sequences s = s.left( s.lastIndexOf( '.' ) ).replace( '_', ' ' ); s = QUrl::fromPercentEncoding( s.toLatin1() ); return s; } bool SqlTrack::inCollection() const { QReadLocker locker( &m_lock ); return m_trackId > 0; } Collections::Collection* SqlTrack::collection() const { return m_collection; } QString SqlTrack::cachedLyrics() const { /* We don't cache the string as it may be potentially very long */ QString query = QString( "SELECT lyrics FROM lyrics WHERE url = %1" ).arg( m_urlId ); QStringList result = m_collection->sqlStorage()->query( query ); if( result.isEmpty() ) return QString(); return result.first(); } void SqlTrack::setCachedLyrics( const QString &lyrics ) { QString query = QString( "SELECT count(*) FROM lyrics WHERE url = %1").arg( m_urlId ); const QStringList queryResult = m_collection->sqlStorage()->query( query ); if( queryResult.isEmpty() ) return; // error in the query? if( queryResult.first().toInt() == 0 ) { QString insert = QString( "INSERT INTO lyrics( url, lyrics ) VALUES ( %1, '%2' )" ) .arg( QString::number( m_urlId ), m_collection->sqlStorage()->escape( lyrics ) ); m_collection->sqlStorage()->insert( insert, "lyrics" ); } else { QString update = QString( "UPDATE lyrics SET lyrics = '%1' WHERE url = %2" ) .arg( m_collection->sqlStorage()->escape( lyrics ), QString::number( m_urlId ) ); m_collection->sqlStorage()->query( update ); } notifyObservers(); } bool SqlTrack::hasCapabilityInterface( Capabilities::Capability::Type type ) const { switch( type ) { case Capabilities::Capability::Actions: case Capabilities::Capability::Organisable: case Capabilities::Capability::BookmarkThis: case Capabilities::Capability::WriteTimecode: case Capabilities::Capability::LoadTimecode: case Capabilities::Capability::ReadLabel: case Capabilities::Capability::WriteLabel: case Capabilities::Capability::FindInSource: return true; default: return Track::hasCapabilityInterface( type ); } } Capabilities::Capability* SqlTrack::createCapabilityInterface( Capabilities::Capability::Type type ) { switch( type ) { case Capabilities::Capability::Actions: { QList actions; //TODO These actions will hang around until m_collection is destructed. // Find a better parent to avoid this memory leak. //actions.append( new CopyToDeviceAction( m_collection, this ) ); return new Capabilities::ActionsCapability( actions ); } case Capabilities::Capability::Organisable: return new Capabilities::OrganiseCapabilityImpl( this ); case Capabilities::Capability::BookmarkThis: return new Capabilities::BookmarkThisCapability( new BookmarkCurrentTrackPositionAction( 0 ) ); case Capabilities::Capability::WriteTimecode: return new Capabilities::TimecodeWriteCapabilityImpl( this ); case Capabilities::Capability::LoadTimecode: return new Capabilities::TimecodeLoadCapabilityImpl( this ); case Capabilities::Capability::ReadLabel: return new Capabilities::SqlReadLabelCapability( this, sqlCollection()->sqlStorage() ); case Capabilities::Capability::WriteLabel: return new Capabilities::SqlWriteLabelCapability( this, sqlCollection()->sqlStorage() ); case Capabilities::Capability::FindInSource: return new Capabilities::FindInSourceCapabilityImpl( this ); default: return Track::createCapabilityInterface( type ); } } void SqlTrack::addLabel( const QString &label ) { Meta::LabelPtr realLabel = m_collection->registry()->getLabel( label ); addLabel( realLabel ); } void SqlTrack::addLabel( const Meta::LabelPtr &label ) { AmarokSharedPointer sqlLabel = AmarokSharedPointer::dynamicCast( label ); if( !sqlLabel ) { Meta::LabelPtr tmp = m_collection->registry()->getLabel( label->name() ); sqlLabel = AmarokSharedPointer::dynamicCast( tmp ); } if( sqlLabel ) { QWriteLocker locker( &m_lock ); commitIfInNonBatchUpdate(); // we need to have a up-to-date m_urlId if( m_urlId <= 0 ) { warning() << "Track does not have an urlId."; return; } QString countQuery = "SELECT COUNT(*) FROM urls_labels WHERE url = %1 AND label = %2;"; QStringList countRs = m_collection->sqlStorage()->query( countQuery.arg( QString::number( m_urlId ), QString::number( sqlLabel->id() ) ) ); if( !countRs.isEmpty() && countRs.first().toInt() == 0 ) { QString insert = "INSERT INTO urls_labels(url,label) VALUES (%1,%2);"; m_collection->sqlStorage()->insert( insert.arg( QString::number( m_urlId ), QString::number( sqlLabel->id() ) ), "urls_labels" ); if( m_labelsInCache ) { m_labelsCache.append( Meta::LabelPtr::staticCast( sqlLabel ) ); } locker.unlock(); notifyObservers(); sqlLabel->invalidateCache(); } } } int SqlTrack::id() const { QReadLocker locker( &m_lock ); return m_trackId; } int SqlTrack::urlId() const { QReadLocker locker( &m_lock ); return m_urlId; } void SqlTrack::removeLabel( const Meta::LabelPtr &label ) { AmarokSharedPointer sqlLabel = AmarokSharedPointer::dynamicCast( label ); if( !sqlLabel ) { Meta::LabelPtr tmp = m_collection->registry()->getLabel( label->name() ); sqlLabel = AmarokSharedPointer::dynamicCast( tmp ); } if( sqlLabel ) { QString query = "DELETE FROM urls_labels WHERE label = %2 and url = (SELECT url FROM tracks WHERE id = %1);"; m_collection->sqlStorage()->query( query.arg( QString::number( m_trackId ), QString::number( sqlLabel->id() ) ) ); if( m_labelsInCache ) { m_labelsCache.removeAll( Meta::LabelPtr::staticCast( sqlLabel ) ); } notifyObservers(); sqlLabel->invalidateCache(); } } Meta::LabelList SqlTrack::labels() const { { QReadLocker locker( &m_lock ); if( m_labelsInCache ) return m_labelsCache; } if( !m_collection ) return Meta::LabelList(); // when running the query maker don't lock. might lead to deadlock via registry Collections::SqlQueryMaker *qm = static_cast< Collections::SqlQueryMaker* >( m_collection->queryMaker() ); qm->setQueryType( Collections::QueryMaker::Label ); qm->addMatch( Meta::TrackPtr( const_cast(this) ) ); qm->setBlocking( true ); qm->run(); { QWriteLocker locker( &m_lock ); m_labelsInCache = true; m_labelsCache = qm->labels(); delete qm; return m_labelsCache; } } TrackEditorPtr SqlTrack::editor() { return TrackEditorPtr( isEditable() ? this : 0 ); } StatisticsPtr SqlTrack::statistics() { return StatisticsPtr( this ); } void SqlTrack::remove() { QWriteLocker locker( &m_lock ); m_cache.clear(); locker.unlock(); m_collection->registry()->removeTrack( m_urlId, m_uid ); // -- inform all albums, artist, years #undef foreachInvalidateCache #define INVALIDATE_AND_UPDATE(X) if( X ) \ { \ X->invalidateCache(); \ X->notifyObservers(); \ } INVALIDATE_AND_UPDATE(static_cast(m_artist.data())); INVALIDATE_AND_UPDATE(static_cast(m_album.data())); INVALIDATE_AND_UPDATE(static_cast(m_composer.data())); INVALIDATE_AND_UPDATE(static_cast(m_genre.data())); INVALIDATE_AND_UPDATE(static_cast(m_year.data())); #undef INVALIDATE_AND_UPDATE m_artist = 0; m_album = 0; m_composer = 0; m_genre = 0; m_year = 0; m_urlId = 0; m_trackId = 0; m_statisticsId = 0; m_collection->collectionUpdated(); } //---------------------- class Artist -------------------------- SqlArtist::SqlArtist( Collections::SqlCollection *collection, int id, const QString &name ) : Artist() , m_collection( collection ) , m_id( id ) , m_name( name ) , m_tracksLoaded( false ) { Q_ASSERT( m_collection ); Q_ASSERT( m_id > 0 ); } Meta::SqlArtist::~SqlArtist() { } void SqlArtist::invalidateCache() { QMutexLocker locker( &m_mutex ); m_tracksLoaded = false; m_tracks.clear(); } TrackList SqlArtist::tracks() { { QMutexLocker locker( &m_mutex ); if( m_tracksLoaded ) return m_tracks; } // when running the query maker don't lock. might lead to deadlock via registry Collections::SqlQueryMaker *qm = static_cast< Collections::SqlQueryMaker* >( m_collection->queryMaker() ); qm->setQueryType( Collections::QueryMaker::Track ); qm->addMatch( Meta::ArtistPtr( this ) ); qm->setBlocking( true ); qm->run(); { QMutexLocker locker( &m_mutex ); m_tracks = qm->tracks(); m_tracksLoaded = true; delete qm; return m_tracks; } } bool SqlArtist::hasCapabilityInterface( Capabilities::Capability::Type type ) const { switch( type ) { case Capabilities::Capability::BookmarkThis: return true; default: return Artist::hasCapabilityInterface( type ); } } Capabilities::Capability* SqlArtist::createCapabilityInterface( Capabilities::Capability::Type type ) { switch( type ) { case Capabilities::Capability::BookmarkThis: return new Capabilities::BookmarkThisCapability( new BookmarkArtistAction( 0, Meta::ArtistPtr( this ) ) ); default: return Artist::createCapabilityInterface( type ); } } //--------------- class Album --------------------------------- const QString SqlAlbum::AMAROK_UNSET_MAGIC = QString( "AMAROK_UNSET_MAGIC" ); SqlAlbum::SqlAlbum( Collections::SqlCollection *collection, int id, const QString &name, int artist ) : Album() , m_collection( collection ) , m_name( name ) , m_id( id ) , m_artistId( artist ) , m_imageId( -1 ) , m_hasImage( false ) , m_hasImageChecked( false ) , m_unsetImageId( -1 ) - , m_tracksLoaded( false ) + , m_tracksLoaded( NotLoaded ) , m_suppressAutoFetch( false ) , m_mutex( QMutex::Recursive ) { Q_ASSERT( m_collection ); Q_ASSERT( m_id > 0 ); } Meta::SqlAlbum::~SqlAlbum() { CoverCache::invalidateAlbum( this ); } void SqlAlbum::invalidateCache() { QMutexLocker locker( &m_mutex ); - m_tracksLoaded = false; + m_tracksLoaded = NotLoaded; m_hasImage = false; m_hasImageChecked = false; m_tracks.clear(); } TrackList SqlAlbum::tracks() { + bool startQuery = false; + { QMutexLocker locker( &m_mutex ); - if( m_tracksLoaded ) + if( m_tracksLoaded == Loaded ) return m_tracks; + else if( m_tracksLoaded == NotLoaded ) + { + startQuery = true; + m_tracksLoaded = Loading; + } } - // when running the query maker don't lock. might lead to deadlock via registry - Collections::SqlQueryMaker *qm = static_cast< Collections::SqlQueryMaker* >( m_collection->queryMaker() ); - qm->setQueryType( Collections::QueryMaker::Track ); - qm->addMatch( Meta::AlbumPtr( this ) ); - qm->orderBy( Meta::valDiscNr ); - qm->orderBy( Meta::valTrackNr ); - qm->orderBy( Meta::valTitle ); - qm->setBlocking( true ); - qm->run(); + if( startQuery ) + { + // when running the query maker don't lock. might lead to deadlock via registry + Collections::SqlQueryMaker *qm = static_cast< Collections::SqlQueryMaker* >( m_collection->queryMaker() ); + qm->setQueryType( Collections::QueryMaker::Track ); + qm->addMatch( Meta::AlbumPtr( this ) ); + qm->orderBy( Meta::valDiscNr ); + qm->orderBy( Meta::valTrackNr ); + qm->orderBy( Meta::valTitle ); + qm->setBlocking( true ); + qm->run(); + { + QMutexLocker locker( &m_mutex ); + m_tracks = qm->tracks(); + m_tracksLoaded = Loaded; + delete qm; + return m_tracks; + } + } + else { - QMutexLocker locker( &m_mutex ); - m_tracks = qm->tracks(); - m_tracksLoaded = true; - delete qm; - return m_tracks; + // Wait for tracks to be loaded + forever + { + QMutexLocker locker( &m_mutex ); + if( m_tracksLoaded == Loaded ) + return m_tracks; + else + QThread::yieldCurrentThread(); + } } } // note for internal implementation: // if hasImage returns true then m_imagePath is set bool SqlAlbum::hasImage( int size ) const { Q_UNUSED(size); // we have every size if we have an image at all QMutexLocker locker( &m_mutex ); if( m_name.isEmpty() ) return false; if( !m_hasImageChecked ) { m_hasImageChecked = true; const_cast( this )->largeImagePath(); // The user has explicitly set no cover if( m_imagePath == AMAROK_UNSET_MAGIC ) m_hasImage = false; // if we don't have an image but it was not explicitly blocked else if( m_imagePath.isEmpty() ) { // Cover fetching runs in another thread. If there is a retrieved cover // then updateImage() gets called which updates the cache and alerts the // subscribers. We use queueAlbum() because this runs the fetch as a // background job and doesn't give an intruding popup asking for confirmation if( !m_suppressAutoFetch && !m_name.isEmpty() && AmarokConfig::autoGetCoverArt() ) CoverFetcher::instance()->queueAlbum( AlbumPtr(const_cast(this)) ); m_hasImage = false; } else m_hasImage = true; } return m_hasImage; } QImage SqlAlbum::image( int size ) const { QMutexLocker locker( &m_mutex ); if( !hasImage() ) return Meta::Album::image( size ); // findCachedImage looks for a scaled version of the fullsize image // which may have been saved on a previous lookup QString cachedImagePath; if( size <= 1 ) cachedImagePath = m_imagePath; else cachedImagePath = scaledDiskCachePath( size ); //FIXME this cache doesn't differentiate between shadowed/unshadowed // a image exists. just load it. if( !cachedImagePath.isEmpty() && QFile( cachedImagePath ).exists() ) { QImage image( cachedImagePath ); if( image.isNull() ) return Meta::Album::image( size ); return image; } // no cached scaled image exists. Have to create it QImage image; // --- embedded cover if( m_collection && m_imagePath.startsWith( m_collection->uidUrlProtocol() ) ) { // -- check if we have a track with the given path as uid Meta::TrackPtr track = m_collection->getTrackFromUid( m_imagePath ); if( track ) image = Meta::Tag::embeddedCover( track->playableUrl().path() ); } // --- a normal path if( image.isNull() ) image = QImage( m_imagePath ); if( image.isNull() ) return Meta::Album::image( size ); if( size > 1 && size < 1000 ) { image = image.scaled( size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation ); - std::thread thread( QOverload::of( &QImage::save ), image, cachedImagePath, "PNG", -1 ); - thread.detach(); + image.save( cachedImagePath, "PNG", -1 ); } return image; } QUrl SqlAlbum::imageLocation( int size ) { if( !hasImage() ) return QUrl(); // findCachedImage looks for a scaled version of the fullsize image // which may have been saved on a previous lookup if( size <= 1 ) return QUrl::fromLocalFile( m_imagePath ); QString cachedImagePath = scaledDiskCachePath( size ); if( cachedImagePath.isEmpty() ) return QUrl(); if( !QFile( cachedImagePath ).exists() ) { // If we don't have the location, it's possible that we haven't tried to find the image yet // So, let's look for it and just ignore the result QImage i = image( size ); Q_UNUSED( i ) } if( !QFile( cachedImagePath ).exists() ) return QUrl(); return QUrl::fromLocalFile(cachedImagePath); } void SqlAlbum::setImage( const QImage &image ) { // the unnamed album is special. it will never have an image if( m_name.isEmpty() ) return; - QMutexLocker locker( &m_mutex ); if( image.isNull() ) return; + QMutexLocker locker( &m_mutex ); + // removeImage() will destroy all scaled cached versions of the artwork // and remove references from the database if required. removeImage(); QString path = largeDiskCachePath(); // make sure not to overwrite existing images while( QFile(path).exists() ) path += '_'; // not that nice but it shouldn't happen that often. - std::thread thread( QOverload::of( &QImage::save ), image, path, "JPG", -1 ); - thread.detach(); + image.save( path, "JPG", -1 ); setImage( path ); locker.unlock(); notifyObservers(); // -- write back the album cover if allowed if( AmarokConfig::writeBackCover() ) { // - scale to cover to a sensible size QImage scaledImage( image ); if( scaledImage.width() > AmarokConfig::writeBackCoverDimensions() || scaledImage.height() > AmarokConfig::writeBackCoverDimensions() ) scaledImage = scaledImage.scaled( AmarokConfig::writeBackCoverDimensions(), AmarokConfig::writeBackCoverDimensions(), Qt::KeepAspectRatio, Qt::SmoothTransformation ); // - set the image for each track Meta::TrackList myTracks = tracks(); foreach( Meta::TrackPtr metaTrack, myTracks ) { // the song needs to be at least one mb big or we won't set an image // that means that the new image will increase the file size by less than 2% if( metaTrack->filesize() > 1024l * 1024l ) { Meta::FieldHash fields; fields.insert( Meta::valImage, scaledImage ); WriteTagsJob *job = new WriteTagsJob( metaTrack->playableUrl().path(), fields ); QObject::connect( job, &WriteTagsJob::done, job, &WriteTagsJob::deleteLater ); ThreadWeaver::Queue::instance()->enqueue( QSharedPointer(job) ); } // note: we might want to update the track file size after writing the image } } } void SqlAlbum::removeImage() { QMutexLocker locker( &m_mutex ); if( !hasImage() ) return; // Update the database image path // Set the album image to a magic value which will tell Amarok not to fetch it automatically const int unsetId = unsetImageId(); QString query = "UPDATE albums SET image = %1 WHERE id = %2"; m_collection->sqlStorage()->query( query.arg( QString::number( unsetId ), QString::number( m_id ) ) ); // From here on we check if there are any remaining references to that particular image in the database // If there aren't, then we should remove the image path from the database ( and possibly delete the file? ) // If there are, we need to leave it since other albums will reference this particular image path. // query = "SELECT count( albums.id ) FROM albums " "WHERE albums.image = %1"; QStringList res = m_collection->sqlStorage()->query( query.arg( QString::number( m_imageId ) ) ); if( !res.isEmpty() ) { int references = res.first().toInt(); // If there are no more references to this particular image, then we should clean up if( references <= 0 ) { query = "DELETE FROM images WHERE id = %1"; m_collection->sqlStorage()->query( query.arg( QString::number( m_imageId ) ) ); // remove the large cover only if it was cached. QDir largeCoverDir( Amarok::saveLocation( "albumcovers/large/" ) ); if( QFileInfo(m_imagePath).absoluteDir() == largeCoverDir ) QFile::remove( m_imagePath ); // remove all cache images QString key = md5sum( QString(), QString(), m_imagePath ); QDir cacheDir( Amarok::saveLocation( "albumcovers/cache/" ) ); QStringList cacheFilter; cacheFilter << QString( "*@" ) + key; QStringList cachedImages = cacheDir.entryList( cacheFilter ); foreach( const QString &image, cachedImages ) { bool r = QFile::remove( cacheDir.filePath( image ) ); debug() << "deleting cached image: " << image << " : " + ( r ? QString("ok") : QString("fail") ); } CoverCache::invalidateAlbum( this ); } } m_imageId = -1; m_imagePath.clear(); m_hasImage = false; m_hasImageChecked = true; locker.unlock(); notifyObservers(); } int SqlAlbum::unsetImageId() const { // Return the cached value if we have already done the lookup before if( m_unsetImageId >= 0 ) return m_unsetImageId; QString query = "SELECT id FROM images WHERE path = '%1'"; QStringList res = m_collection->sqlStorage()->query( query.arg( AMAROK_UNSET_MAGIC ) ); // We already have the AMAROK_UNSET_MAGIC variable in the database if( !res.isEmpty() ) { m_unsetImageId = res.first().toInt(); } else { // We need to create this value query = QString( "INSERT INTO images( path ) VALUES ( '%1' )" ) .arg( m_collection->sqlStorage()->escape( AMAROK_UNSET_MAGIC ) ); m_unsetImageId = m_collection->sqlStorage()->insert( query, "images" ); } return m_unsetImageId; } bool SqlAlbum::isCompilation() const { return !hasAlbumArtist(); } bool SqlAlbum::hasAlbumArtist() const { return !albumArtist().isNull(); } Meta::ArtistPtr SqlAlbum::albumArtist() const { if( m_artistId > 0 && !m_artist ) { const_cast( this )->m_artist = m_collection->registry()->getArtist( m_artistId ); } return m_artist; } QByteArray SqlAlbum::md5sum( const QString& artist, const QString& album, const QString& file ) const { // FIXME: All existing image stores have been invalidated. return QCryptographicHash::hash( artist.toLower().toUtf8() + QByteArray( "#" ) + album.toLower().toUtf8() + QByteArray( "?" ) + file.toUtf8(), QCryptographicHash::Md5 ).toHex(); } QString SqlAlbum::largeDiskCachePath() const { // IMPROVEMENT: the large disk cache path could be human readable const QString artist = hasAlbumArtist() ? albumArtist()->name() : QString(); if( artist.isEmpty() && m_name.isEmpty() ) return QString(); QDir largeCoverDir( Amarok::saveLocation( "albumcovers/large/" ) ); const QString key = md5sum( artist, m_name, QString() ); if( !key.isEmpty() ) return largeCoverDir.filePath( key ); return QString(); } QString SqlAlbum::scaledDiskCachePath( int size ) const { const QByteArray widthKey = QByteArray::number( size ) + '@'; QDir cacheCoverDir( Amarok::saveLocation( "albumcovers/cache/" ) ); QString key = md5sum( QString(), QString(), m_imagePath ); if( !cacheCoverDir.exists( widthKey + key ) ) { // the correct location is empty // check deprecated locations for the image cache and delete them // (deleting the scaled image cache is fine) const QString artist = hasAlbumArtist() ? albumArtist()->name() : QString(); if( artist.isEmpty() && m_name.isEmpty() ) ; // do nothing special else { QString oldKey; oldKey = md5sum( artist, m_name, m_imagePath ); if( cacheCoverDir.exists( widthKey + oldKey ) ) cacheCoverDir.remove( widthKey + oldKey ); oldKey = md5sum( artist, m_name, QString() ); if( cacheCoverDir.exists( widthKey + oldKey ) ) cacheCoverDir.remove( widthKey + oldKey ); } } return cacheCoverDir.filePath( widthKey + key ); } QString SqlAlbum::largeImagePath() { if( !m_collection ) return m_imagePath; // Look up in the database QString query = "SELECT images.id, images.path FROM images, albums WHERE albums.image = images.id AND albums.id = %1;"; // TODO: shouldn't we do a JOIN here? QStringList res = m_collection->sqlStorage()->query( query.arg( m_id ) ); if( !res.isEmpty() ) { m_imageId = res.at(0).toInt(); m_imagePath = res.at(1); // explicitly deleted image if( m_imagePath == AMAROK_UNSET_MAGIC ) return AMAROK_UNSET_MAGIC; // embedded image (e.g. id3v2 APIC // We store embedded images as unique ids in the database // we will get the real image later on from the track. if( m_imagePath.startsWith( m_collection->uidUrlProtocol()+"://" ) ) return m_imagePath; // normal file if( !m_imagePath.isEmpty() && QFile::exists( m_imagePath ) ) return m_imagePath; } // After a rescan we currently lose all image information, so we need // to check that we haven't already downloaded this image before. m_imagePath = largeDiskCachePath(); if( !m_imagePath.isEmpty() && QFile::exists( m_imagePath ) ) { setImage(m_imagePath); return m_imagePath; } m_imageId = -1; m_imagePath.clear(); return m_imagePath; } // note: we won't notify the observers. we are a private function. the caller must do that. void SqlAlbum::setImage( const QString &path ) { - if( m_imagePath == path ) - return; if( m_name.isEmpty() ) // the empty album never has an image return; QMutexLocker locker( &m_mutex ); - QString imagePath = path; + if( m_imagePath == path ) + return; QString query = "SELECT id FROM images WHERE path = '%1'"; - query = query.arg( m_collection->sqlStorage()->escape( imagePath ) ); + query = query.arg( m_collection->sqlStorage()->escape( path ) ); QStringList res = m_collection->sqlStorage()->query( query ); if( res.isEmpty() ) { QString insert = QString( "INSERT INTO images( path ) VALUES ( '%1' )" ) - .arg( m_collection->sqlStorage()->escape( imagePath ) ); + .arg( m_collection->sqlStorage()->escape( path ) ); m_imageId = m_collection->sqlStorage()->insert( insert, "images" ); } else m_imageId = res.first().toInt(); if( m_imageId >= 0 ) { query = QString("UPDATE albums SET image = %1 WHERE albums.id = %2" ) .arg( QString::number( m_imageId ), QString::number( m_id ) ); m_collection->sqlStorage()->query( query ); - m_imagePath = imagePath; + m_imagePath = path; m_hasImage = true; m_hasImageChecked = true; CoverCache::invalidateAlbum( this ); } } /** Set the compilation flag. * Actually it does not cange this album but instead moves * the tracks to other albums (e.g. one with the same name which is a * compilation) * If the compilation flag is set to "false" then all songs * with different artists will be moved to other albums, possibly even * creating them. */ void SqlAlbum::setCompilation( bool compilation ) { if( m_name.isEmpty() ) return; if( isCompilation() == compilation ) { return; } else { m_collection->blockUpdatedSignal(); if( compilation ) { // get the new compilation album Meta::AlbumPtr metaAlbum = m_collection->registry()->getAlbum( name(), QString() ); AmarokSharedPointer sqlAlbum = AmarokSharedPointer::dynamicCast( metaAlbum ); Meta::FieldHash changes; changes.insert( Meta::valCompilation, 1); Meta::TrackList myTracks = tracks(); foreach( Meta::TrackPtr metaTrack, myTracks ) { SqlTrack* sqlTrack = static_cast(metaTrack.data()); // copy over the cover image if( sqlTrack->album()->hasImage() && !sqlAlbum->hasImage() ) sqlAlbum->setImage( sqlTrack->album()->imageLocation().path() ); // move the track sqlTrack->setAlbum( sqlAlbum->id() ); if( AmarokConfig::writeBack() ) Meta::Tag::writeTags( sqlTrack->playableUrl().path(), changes, AmarokConfig::writeBackStatistics() ); } /* TODO: delete all old tracks albums */ } else { Meta::FieldHash changes; changes.insert( Meta::valCompilation, 0); Meta::TrackList myTracks = tracks(); foreach( Meta::TrackPtr metaTrack, myTracks ) { SqlTrack* sqlTrack = static_cast(metaTrack.data()); Meta::ArtistPtr trackArtist = sqlTrack->artist(); // get the new album Meta::AlbumPtr metaAlbum = m_collection->registry()->getAlbum( sqlTrack->album()->name(), trackArtist ? ArtistHelper::realTrackArtist( trackArtist->name() ) : QString() ); AmarokSharedPointer sqlAlbum = AmarokSharedPointer::dynamicCast( metaAlbum ); // copy over the cover image if( sqlTrack->album()->hasImage() && !sqlAlbum->hasImage() ) sqlAlbum->setImage( sqlTrack->album()->imageLocation().path() ); // move the track sqlTrack->setAlbum( sqlAlbum->id() ); if( AmarokConfig::writeBack() ) Meta::Tag::writeTags( sqlTrack->playableUrl().path(), changes, AmarokConfig::writeBackStatistics() ); } /* TODO //step 5: delete the original album, if necessary */ } m_collection->unblockUpdatedSignal(); } } bool SqlAlbum::hasCapabilityInterface( Capabilities::Capability::Type type ) const { if( m_name.isEmpty() ) return false; switch( type ) { case Capabilities::Capability::Actions: case Capabilities::Capability::BookmarkThis: return true; default: return Album::hasCapabilityInterface( type ); } } Capabilities::Capability* SqlAlbum::createCapabilityInterface( Capabilities::Capability::Type type ) { if( m_name.isEmpty() ) return 0; switch( type ) { case Capabilities::Capability::Actions: return new Capabilities::AlbumActionsCapability( Meta::AlbumPtr( this ) ); case Capabilities::Capability::BookmarkThis: return new Capabilities::BookmarkThisCapability( new BookmarkAlbumAction( 0, Meta::AlbumPtr( this ) ) ); default: return Album::createCapabilityInterface( type ); } } //---------------SqlComposer--------------------------------- SqlComposer::SqlComposer( Collections::SqlCollection *collection, int id, const QString &name ) : Composer() , m_collection( collection ) , m_id( id ) , m_name( name ) , m_tracksLoaded( false ) { Q_ASSERT( m_collection ); Q_ASSERT( m_id > 0 ); } void SqlComposer::invalidateCache() { QMutexLocker locker( &m_mutex ); m_tracksLoaded = false; m_tracks.clear(); } TrackList SqlComposer::tracks() { { QMutexLocker locker( &m_mutex ); if( m_tracksLoaded ) return m_tracks; } Collections::SqlQueryMaker *qm = static_cast< Collections::SqlQueryMaker* >( m_collection->queryMaker() ); qm->setQueryType( Collections::QueryMaker::Track ); qm->addMatch( Meta::ComposerPtr( this ) ); qm->setBlocking( true ); qm->run(); { QMutexLocker locker( &m_mutex ); m_tracks = qm->tracks(); m_tracksLoaded = true; delete qm; return m_tracks; } } //---------------SqlGenre--------------------------------- SqlGenre::SqlGenre( Collections::SqlCollection *collection, int id, const QString &name ) : Genre() , m_collection( collection ) , m_id( id ) , m_name( name ) , m_tracksLoaded( false ) { Q_ASSERT( m_collection ); Q_ASSERT( m_id > 0 ); } void SqlGenre::invalidateCache() { QMutexLocker locker( &m_mutex ); m_tracksLoaded = false; m_tracks.clear(); } TrackList SqlGenre::tracks() { { QMutexLocker locker( &m_mutex ); if( m_tracksLoaded ) return m_tracks; } // when running the query maker don't lock. might lead to deadlock via registry Collections::SqlQueryMaker *qm = static_cast< Collections::SqlQueryMaker* >( m_collection->queryMaker() ); qm->setQueryType( Collections::QueryMaker::Track ); qm->addMatch( Meta::GenrePtr( this ) ); qm->setBlocking( true ); qm->run(); { QMutexLocker locker( &m_mutex ); m_tracks = qm->tracks(); m_tracksLoaded = true; delete qm; return m_tracks; } } //---------------SqlYear--------------------------------- SqlYear::SqlYear( Collections::SqlCollection *collection, int id, int year) : Year() , m_collection( collection ) , m_id( id ) , m_year( year ) , m_tracksLoaded( false ) { Q_ASSERT( m_collection ); Q_ASSERT( m_id > 0 ); } void SqlYear::invalidateCache() { QMutexLocker locker( &m_mutex ); m_tracksLoaded = false; m_tracks.clear(); } TrackList SqlYear::tracks() { { QMutexLocker locker( &m_mutex ); if( m_tracksLoaded ) return m_tracks; } // when running the query maker don't lock. might lead to deadlock via registry Collections::SqlQueryMaker *qm = static_cast< Collections::SqlQueryMaker* >( m_collection->queryMaker() ); qm->setQueryType( Collections::QueryMaker::Track ); qm->addMatch( Meta::YearPtr( this ) ); qm->setBlocking( true ); qm->run(); { QMutexLocker locker( &m_mutex ); m_tracks = qm->tracks(); m_tracksLoaded = true; delete qm; return m_tracks; } } //---------------SqlLabel--------------------------------- SqlLabel::SqlLabel( Collections::SqlCollection *collection, int id, const QString &name ) : Label() , m_collection( collection ) , m_id( id ) , m_name( name ) , m_tracksLoaded( false ) { Q_ASSERT( m_collection ); Q_ASSERT( m_id > 0 ); } void SqlLabel::invalidateCache() { QMutexLocker locker( &m_mutex ); m_tracksLoaded = false; m_tracks.clear(); } TrackList SqlLabel::tracks() { { QMutexLocker locker( &m_mutex ); if( m_tracksLoaded ) return m_tracks; } // when running the query maker don't lock. might lead to deadlock via registry Collections::SqlQueryMaker *qm = static_cast< Collections::SqlQueryMaker* >( m_collection->queryMaker() ); qm->setQueryType( Collections::QueryMaker::Track ); qm->addMatch( Meta::LabelPtr( this ) ); qm->setBlocking( true ); qm->run(); { QMutexLocker locker( &m_mutex ); m_tracks = qm->tracks(); m_tracksLoaded = true; delete qm; return m_tracks; } } diff --git a/src/core-impl/collections/db/sql/SqlMeta.h b/src/core-impl/collections/db/sql/SqlMeta.h index d4b3d6f4f1..dd00ec5e4b 100644 --- a/src/core-impl/collections/db/sql/SqlMeta.h +++ b/src/core-impl/collections/db/sql/SqlMeta.h @@ -1,586 +1,593 @@ /**************************************************************************************** * Copyright (c) 2007 Maximilian Kossick * * Copyright (c) 2007 Alexandre Pereira de Oliveira * * Copyright (c) 2010 Ralf Engels * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #ifndef SQLMETA_H #define SQLMETA_H #include "core/meta/Meta.h" #include "core/meta/Statistics.h" #include "core/meta/TrackEditor.h" #include "core/meta/support/MetaConstants.h" #include "amarok_sqlcollection_export.h" #include "FileType.h" +#include #include #include #include #include #include #include namespace Capabilities { class AlbumCapabilityDelegate; class ArtistCapabilityDelegate; class TrackCapabilityDelegate; } class QAction; class SqlRegistry; class TrackUrlsTableCommitter; class TrackTracksTableCommitter; class TrackStatisticsTableCommitter; namespace Collections { class SqlCollection; } class SqlScanResultProcessor; namespace Meta { /** The SqlTrack is a Meta::Track used by the SqlCollection. The SqlTrack has a couple of functions for writing values and also some functions for getting e.g. the track id used in the underlying database. However it is not recommended to interface with the database directly. The whole class should be thread save. */ class AMAROK_SQLCOLLECTION_EXPORT SqlTrack : public Track, public Statistics, public TrackEditor { public: /** Creates a new SqlTrack without. * Note that the trackId and urlId are empty meaning that this track * has no database representation until it's written first by setting * some of the meta information. * It is advisable to set at least the path. */ SqlTrack( Collections::SqlCollection *collection, int deviceId, const QString &rpath, int directoryId, const QString &uidUrl ); SqlTrack( Collections::SqlCollection *collection, const QStringList &queryResult ); ~ SqlTrack(); virtual QString name() const; virtual QString prettyName() const; virtual QUrl playableUrl() const; virtual QString prettyUrl() const; virtual QString uidUrl() const; virtual QString notPlayableReason() const; virtual Meta::AlbumPtr album() const; virtual Meta::ArtistPtr artist() const; virtual Meta::ComposerPtr composer() const; virtual Meta::YearPtr year() const; virtual Meta::GenrePtr genre() const; virtual QString type() const; virtual qreal bpm() const; virtual QString comment() const; virtual qint64 length() const; virtual int filesize() const; virtual int sampleRate() const; virtual int bitrate() const; virtual QDateTime createDate() const; virtual QDateTime modifyDate() const; virtual int trackNumber() const; virtual int discNumber() const; virtual qreal replayGain( Meta::ReplayGainTag mode ) const; virtual bool inCollection() const; virtual Collections::Collection* collection() const; virtual QString cachedLyrics() const; virtual void setCachedLyrics( const QString &lyrics ); virtual bool hasCapabilityInterface( Capabilities::Capability::Type type ) const; virtual Capabilities::Capability* createCapabilityInterface( Capabilities::Capability::Type type ); virtual void addLabel( const QString &label ); virtual void addLabel( const Meta::LabelPtr &label ); virtual void removeLabel( const Meta::LabelPtr &label ); virtual Meta::LabelList labels() const; virtual TrackEditorPtr editor(); virtual StatisticsPtr statistics(); // Meta::TrackEditor methods: virtual void setAlbum( const QString &newAlbum ); virtual void setAlbumArtist( const QString &newAlbumArtist ); virtual void setArtist( const QString &newArtist ); virtual void setComposer( const QString &newComposer ); virtual void setGenre( const QString &newGenre ); virtual void setYear( int newYear ); virtual void setTitle( const QString &newTitle ); virtual void setComment( const QString &newComment ); virtual void setTrackNumber( int newTrackNumber ); virtual void setDiscNumber( int newDiscNumber ); virtual void setBpm( const qreal newBpm ); // Meta::Statistics methods: virtual double score() const; virtual void setScore( double newScore ); virtual int rating() const; virtual void setRating( int newRating ); virtual QDateTime firstPlayed() const; virtual void setFirstPlayed( const QDateTime &newTime ); virtual QDateTime lastPlayed() const; virtual void setLastPlayed( const QDateTime &newTime ); virtual int playCount() const; virtual void setPlayCount( const int newCount ); // combined Meta::Statistics and Meta::TrackEditor methods: virtual void beginUpdate(); virtual void endUpdate(); // SqlTrack specific methods /** true if there is a collection, the file exists on disk and is writable */ bool isEditable() const; void setUidUrl( const QString &uid ); void setAlbum( int albumId ); void setType( Amarok::FileType newType ); void setLength( qint64 newLength ); void setSampleRate( int newSampleRate ); void setUrl( int deviceId, const QString &rpath, int directoryId ); void setBitrate( int newBitrate ); void setModifyDate( const QDateTime &newTime ); void setReplayGain( Meta::ReplayGainTag mode, qreal value ); /** Enables or disables writing changes to the file. * This function can be useful when changes are imported from the file. * In such a case writing the changes back again is stupid. */ virtual void setWriteFile( const bool enable ) { m_writeFile = enable; } int id() const; int urlId() const; Collections::SqlCollection* sqlCollection() const { return m_collection; } /** Does it's best to remove the track from database. * Considered that there is no signal that says "I am now removed" * this function still tries it's best to notify everyone * That the track is now removed, plus it will also delete it from * the database. */ void remove(); // SqlDatabase specific values /** Some numbers used in SqlRegistry. * Update if getTrackReturnValues is updated. */ enum TrackReturnIndex { returnIndex_urlId = 0, returnIndex_urlDeviceId = 1, returnIndex_urlRPath = 2, returnIndex_urlUid = 4, returnIndex_trackId = 5 }; // SqlDatabase specific values /** returns a string of all database values that can be fetched for a track */ static QString getTrackReturnValues(); /** returns the number of return values in getTrackReturnValues() */ static int getTrackReturnValueCount(); /** returns a string of all database joins that are required to fetch all values for a track*/ static QString getTrackJoinConditions(); protected: /** * Will commit all changes in m_cache if m_batch == 0. Must be called with m_lock * locked for writing. * * commitIfInNonBatchUpdate() will do three things: * 1. It will update the member variables. * 2. It will call all write methods * 3. It will notify all observers and the collection about the changes. */ void commitIfInNonBatchUpdate( qint64 field, const QVariant &value ); void commitIfInNonBatchUpdate(); void updatePlaylistsToDb( const FieldHash &fields, const QString &oldUid ); void updateEmbeddedCoversToDb( const FieldHash &fields, const QString &oldUid ); private: //helper functions static QString prettyTitle( const QString &filename ); Collections::SqlCollection* const m_collection; QString m_title; // the url table int m_urlId; int m_deviceId; QString m_rpath; int m_directoryId; // only set when the urls table needs to be written QUrl m_url; QString m_uid; // the rest int m_trackId; int m_statisticsId; qint64 m_length; qint64 m_filesize; int m_trackNumber; int m_discNumber; QDateTime m_lastPlayed; QDateTime m_firstPlayed; int m_playCount; int m_bitrate; int m_sampleRate; int m_rating; double m_score; QString m_comment; qreal m_bpm; qreal m_albumGain; qreal m_albumPeakGain; qreal m_trackGain; qreal m_trackPeakGain; QDateTime m_createDate; QDateTime m_modifyDate; Meta::AlbumPtr m_album; Meta::ArtistPtr m_artist; Meta::GenrePtr m_genre; Meta::ComposerPtr m_composer; Meta::YearPtr m_year; Amarok::FileType m_filetype; /** * Number of current batch operations started by @see beginUpdate() and not * yet ended by @see endUpdate(). Must only be accessed with m_lock held. */ int m_batchUpdate; bool m_writeFile; bool m_writeAllStatisticsFields; FieldHash m_cache; /** This ReadWriteLock is protecting all internal variables. It is ensuring that m_cache, m_batchUpdate and the othre internal variable are in a consistent state all the time. */ mutable QReadWriteLock m_lock; mutable bool m_labelsInCache; mutable Meta::LabelList m_labelsCache; friend class ::SqlRegistry; // needs to call notifyObservers friend class ::TrackUrlsTableCommitter; friend class ::TrackTracksTableCommitter; friend class ::TrackStatisticsTableCommitter; }; class AMAROK_SQLCOLLECTION_EXPORT SqlArtist : public Meta::Artist { public: SqlArtist( Collections::SqlCollection* collection, int id, const QString &name ); ~SqlArtist(); virtual QString name() const { return m_name; } virtual void invalidateCache(); virtual Meta::TrackList tracks(); virtual bool hasCapabilityInterface( Capabilities::Capability::Type type ) const; virtual Capabilities::Capability* createCapabilityInterface( Capabilities::Capability::Type type ); //SQL specific methods int id() const { return m_id; } private: Collections::SqlCollection* const m_collection; const int m_id; const QString m_name; bool m_tracksLoaded; Meta::TrackList m_tracks; QMutex m_mutex; friend class ::SqlRegistry; // needs to call notifyObservers friend class Meta::SqlTrack; // needs to call notifyObservers }; /** Represents an albums stored in the database. Note: The album without name is special. It will always be a compilation and never have a picture. */ class AMAROK_SQLCOLLECTION_EXPORT SqlAlbum : public Meta::Album { public: SqlAlbum( Collections::SqlCollection* collection, int id, const QString &name, int artist ); ~SqlAlbum(); virtual QString name() const { return m_name; } virtual void invalidateCache(); virtual Meta::TrackList tracks(); virtual bool isCompilation() const; virtual bool canUpdateCompilation() const { return true; } void setCompilation( bool compilation ); /** Returns true if this album has an artist. * The following equation is always true: isCompilation() != hasAlbumArtist() */ virtual bool hasAlbumArtist() const; /** Returns the album artist. * Note that setting the album artist is not supported. * A compilation does not have an artist and not only an empty artist. */ virtual Meta::ArtistPtr albumArtist() const; //updating album images is possible for local tracks, but let's ignore it for now /** Returns true if the album has a cover image. * @param size The maximum width or height of the result. * when size is <= 1, return the full size image */ virtual bool hasImage(int size = 0) const; virtual bool canUpdateImage() const { return true; } /** Returns the album cover image. * Returns a default image if no specific album image could be found. * In such a case it will start the cover fetcher. * * @param size is the maximum width or height of the resulting image. * when size is <= 1, return the full size image */ virtual QImage image( int size = 0 ) const; virtual QUrl imageLocation( int size = 0 ); virtual void setImage( const QImage &image ); virtual void removeImage(); virtual void setSuppressImageAutoFetch( const bool suppress ) { m_suppressAutoFetch = suppress; } virtual bool suppressImageAutoFetch() const { return m_suppressAutoFetch; } virtual bool hasCapabilityInterface( Capabilities::Capability::Type type ) const; virtual Capabilities::Capability* createCapabilityInterface( Capabilities::Capability::Type type ); //SQL specific methods int id() const { return m_id; } Collections::SqlCollection *sqlCollection() const { return m_collection; } private: QByteArray md5sum( const QString& artist, const QString& album, const QString& file ) const; /** Returns a unique key for the album cover. */ QByteArray imageKey() const; /** Returns the path that the large scale image should have on the disk * Does not check if the file exists. * Note: not all large images have a disk cache, e.g. if they are set from outside * or embedded inside an audio file. * The largeDiskCache is only used for images set via setImage(QImage) */ QString largeDiskCachePath() const; /** Returns the path that the image should have on the disk * Does not check if the file exists. * @param size is the maximum width or height of the resulting image. * size==0 is the large image and the location of this file is completely different. * there should never be a scaled cached version of the large image. it dose not make * sense. */ QString scaledDiskCachePath( int size ) const; /** Returns the path to the large image * Queries the database for the path of the large scale image. */ QString largeImagePath(); /** Updates the database * Sets the current albums image to the given path. * The path should point to a valid image. * Note: setImage will not delete the already set image. */ - void setImage( const QString &path ); + void setImage( const QString &path ); - /** Finds or creates a magic value in the database which tells Amarok not to auto fetch an image since it has been explicitly unset. - */ - int unsetImageId() const; + /** Finds or creates a magic value in the database which tells Amarok not to auto fetch an image since it has been explicitly unset. + */ + int unsetImageId() const; - private: Collections::SqlCollection* const m_collection; + enum TracksLoadingStatus + { + NotLoaded, + Loading, + Loaded + }; - QString m_name; - int m_id; // the id of this album in the database - int m_artistId; + const QString m_name; + const int m_id; // the id of this album in the database + const int m_artistId; int m_imageId; mutable QString m_imagePath; // path read from the database mutable bool m_hasImage; // true if we have an original image mutable bool m_hasImageChecked; // true if hasImage was checked mutable int m_unsetImageId; // this is the id of the unset magic value in the image sql database static const QString AMAROK_UNSET_MAGIC; - bool m_tracksLoaded; + TracksLoadingStatus m_tracksLoaded; bool m_suppressAutoFetch; Meta::ArtistPtr m_artist; Meta::TrackList m_tracks; mutable QMutex m_mutex; //TODO: add album artist friend class ::SqlRegistry; // needs to call notifyObservers friend class Meta::SqlTrack; // needs to set images directly friend class ::SqlScanResultProcessor; // needs to set images directly }; class AMAROK_SQLCOLLECTION_EXPORT SqlComposer : public Meta::Composer { public: SqlComposer( Collections::SqlCollection* collection, int id, const QString &name ); virtual QString name() const { return m_name; } virtual void invalidateCache(); virtual Meta::TrackList tracks(); //SQL specific methods int id() const { return m_id; } private: Collections::SqlCollection* const m_collection; const int m_id; const QString m_name; bool m_tracksLoaded; + bool m_tracksLoading; Meta::TrackList m_tracks; QMutex m_mutex; friend class ::SqlRegistry; // needs to call notifyObservers friend class Meta::SqlTrack; // needs to call notifyObservers }; class SqlGenre : public Meta::Genre { public: SqlGenre( Collections::SqlCollection* collection, int id, const QString &name ); virtual QString name() const { return m_name; } /** Invalidates the tracks cache */ /** Invalidates the tracks cache */ virtual void invalidateCache(); virtual Meta::TrackList tracks(); //SQL specific methods int id() const { return m_id; } private: Collections::SqlCollection* const m_collection; const int m_id; const QString m_name; bool m_tracksLoaded; Meta::TrackList m_tracks; QMutex m_mutex; friend class ::SqlRegistry; // needs to call notifyObservers friend class Meta::SqlTrack; // needs to call notifyObservers }; class AMAROK_SQLCOLLECTION_EXPORT SqlYear : public Meta::Year { public: SqlYear( Collections::SqlCollection* collection, int id, int year ); virtual QString name() const { return QString::number(m_year); } virtual int year() const { return m_year; } /** Invalidates the tracks cache */ virtual void invalidateCache(); virtual Meta::TrackList tracks(); //SQL specific methods int id() const { return m_id; } private: Collections::SqlCollection* const m_collection; const int m_id; const int m_year; bool m_tracksLoaded; Meta::TrackList m_tracks; QMutex m_mutex; friend class ::SqlRegistry; // needs to call notifyObservers friend class Meta::SqlTrack; // needs to call notifyObservers }; class AMAROK_SQLCOLLECTION_EXPORT SqlLabel : public Meta::Label { public: SqlLabel( Collections::SqlCollection *collection, int id, const QString &name ); virtual QString name() const { return m_name; } /** Invalidates the tracks cache */ virtual void invalidateCache(); virtual Meta::TrackList tracks(); //SQL specific methods int id() const { return m_id; } private: Collections::SqlCollection* const m_collection; const int m_id; const QString m_name; bool m_tracksLoaded; Meta::TrackList m_tracks; QMutex m_mutex; friend class ::SqlRegistry; // needs to call notifyObservers friend class Meta::SqlTrack; // needs to call notifyObservers }; typedef AmarokSharedPointer SqlTrackPtr; typedef AmarokSharedPointer SqlArtistPtr; typedef AmarokSharedPointer SqlAlbumPtr; typedef AmarokSharedPointer SqlComposerPtr; typedef AmarokSharedPointer SqlGenrePtr; typedef AmarokSharedPointer SqlYearPtr; } #endif /* SQLMETA_H */ diff --git a/src/core-impl/collections/ipodcollection/jobs/IpodCopyTracksJob.h b/src/core-impl/collections/ipodcollection/jobs/IpodCopyTracksJob.h index 84f283199e..4cbf91dc4d 100644 --- a/src/core-impl/collections/ipodcollection/jobs/IpodCopyTracksJob.h +++ b/src/core-impl/collections/ipodcollection/jobs/IpodCopyTracksJob.h @@ -1,124 +1,124 @@ /**************************************************************************************** * Copyright (c) 2012 Matěj Laitl * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #ifndef COPYTRACKSJOB_H #define COPYTRACKSJOB_H #include "IpodCollection.h" #include "core/meta/forward_declarations.h" #include "core/transcoding/TranscodingConfiguration.h" #include #include #include class KJob; class IpodCopyTracksJob : public QObject, public ThreadWeaver::Job { Q_OBJECT public: enum CopiedStatus { Duplicate, /// a track with same meta-data is already in iPod collection ExceededingSafeCapacity, /// would exceed "safe" capacity NotPlayable, /// track format would not be playable on connected iPod CopyingFailed, /// KIO failed to copy the file InternalError, /// all other reasons that have no nice user-tellable reason Success /// copied successfully }; /** * @param goingToRemoveSources whether this is in fact a move operation */ IpodCopyTracksJob( const QMap &sources, const QPointer &collection, const Transcoding::Configuration &configuration, bool goingToRemoveSources ); - void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) Q_DECL_OVERRIDE; + void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) override; public Q_SLOTS: void abort(); Q_SIGNALS: // a hack to create QueryMaken in a thread with event loop: void startDuplicateTrackSearch( const Meta::TrackPtr &track ); // a hack to create copyjob in a thread with event loop: void startCopyOrTranscodeJob( const QUrl &src, const QUrl &dest, bool isJustCopy ); // a hack to display KMessageBox in a gui thread: void displaySorryDialog(); // signals for progress operation: void incrementProgress(); void endProgressOperation( QObject *obj ); void totalSteps( int steps ); // not used, defined to keep QObject::conect warning quiet /** * Signal various track copy statuses back to IpodCollectionLocation * @param srcTrack source track, always non-nul * @param destTrack destination track on iPod, copied one or existing if * status == Duplicate; may be null * @param status copying status */ void signalTrackProcessed( Meta::TrackPtr srcTrack, Meta::TrackPtr destTrack, IpodCopyTracksJob::CopiedStatus status ); /** 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); private Q_SLOTS: /// @see startDuplicateTrackSearch() void slotStartDuplicateTrackSearch( const Meta::TrackPtr &track ); void slotDuplicateTrackSearchNewResult( const Meta::TrackList &tracks ); void slotDuplicateTrackSearchQueryDone(); /// @see startCopyJob() void slotStartCopyOrTranscodeJob( const QUrl &sourceUrl, const QUrl &destUrl, bool isJustCopy ); void slotCopyOrTranscodeJobFinished( KJob *job ); /// @see displaySorryDialog() void slotDisplaySorryDialog(); private: void trackProcessed( CopiedStatus status, Meta::TrackPtr srcTrack, Meta::TrackPtr destTrack = Meta::TrackPtr() ); QPointer m_coll; Transcoding::Configuration m_transcodingConfig; QMap m_sources; QMultiHash m_sourceTrackStatus; QSemaphore m_copying; QSemaphore m_searchingForDuplicates; Meta::TrackPtr m_duplicateTrack; bool m_aborted; bool m_goingToRemoveSources; QSet m_notPlayableFormats; QSet m_copyErrors; protected: - void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; + void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; }; #endif // COPYTRACKSJOB_H diff --git a/src/core-impl/collections/ipodcollection/jobs/IpodDeleteTracksJob.h b/src/core-impl/collections/ipodcollection/jobs/IpodDeleteTracksJob.h index b061121a50..99dbcee1ad 100644 --- a/src/core-impl/collections/ipodcollection/jobs/IpodDeleteTracksJob.h +++ b/src/core-impl/collections/ipodcollection/jobs/IpodDeleteTracksJob.h @@ -1,58 +1,58 @@ /**************************************************************************************** * 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 IPODDELETETRACKSJOB_H #define IPODDELETETRACKSJOB_H #include "IpodCollection.h" #include "core/meta/forward_declarations.h" #include class IpodDeleteTracksJob : public QObject, public ThreadWeaver::Job { Q_OBJECT public: explicit IpodDeleteTracksJob( const Meta::TrackList &sources, const QPointer &collection ); - void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) Q_DECL_OVERRIDE; + void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) override; Q_SIGNALS: // signals for progress operation: void incrementProgress(); void endProgressOperation( QObject *obj ); void totalSteps( int steps ); // not used, defined to keep QObject::conect warning quiet /** 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); protected: - void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; + void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; private: Meta::TrackList m_sources; QPointer m_coll; }; #endif // IPODDELETETRACKSJOB_H diff --git a/src/core-impl/collections/ipodcollection/jobs/IpodParseTracksJob.h b/src/core-impl/collections/ipodcollection/jobs/IpodParseTracksJob.h index 6882ec9539..950bc764bf 100644 --- a/src/core-impl/collections/ipodcollection/jobs/IpodParseTracksJob.h +++ b/src/core-impl/collections/ipodcollection/jobs/IpodParseTracksJob.h @@ -1,80 +1,80 @@ /**************************************************************************************** * 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 IPODPARSETRACKSJOB_H #define IPODPARSETRACKSJOB_H #include "core/meta/forward_declarations.h" #include class IpodCollection; /** * A job designed to parse iPod tracks and playlists in a thread so that main thread is * not blocked with it. It is guaranteed by IpodCollection that is doesn't destory itself * while this job is alive. Memory management of this job is up to the caller of it. */ class IpodParseTracksJob : public QObject, public ThreadWeaver::Job { Q_OBJECT public: explicit IpodParseTracksJob( IpodCollection *collection ); public Q_SLOTS: /** * Aborts the job as soon as it is safely possible */ void abort(); protected: - void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) Q_DECL_OVERRIDE; + void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) override; Q_SIGNALS: // signals for progress operation: void incrementProgress(); void endProgressOperation( QObject *obj ); void totalSteps( int steps ); // not used, defined to keep QObject::conect warning quiet /** 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); private: /** * Go through iPod playlists and create Amarok playlists for them. * * @param staleTracks list of track from iTunes database whose associated file * no longer exists * @param knownPaths a set of absolute local paths of all track from iTunes * database; used for orphaned tracks detection */ void parsePlaylists( const Meta::TrackList &staleTracks, const QSet &knownPaths ); Meta::TrackList findOrphanedTracks( const QSet &knownPaths ); IpodCollection *m_coll; bool m_aborted; }; #endif // IPODPARSETRACKSJOB_H diff --git a/src/core-impl/collections/ipodcollection/jobs/IpodWriteDatabaseJob.h b/src/core-impl/collections/ipodcollection/jobs/IpodWriteDatabaseJob.h index 0859b4d41c..9dfc811f8e 100644 --- a/src/core-impl/collections/ipodcollection/jobs/IpodWriteDatabaseJob.h +++ b/src/core-impl/collections/ipodcollection/jobs/IpodWriteDatabaseJob.h @@ -1,55 +1,55 @@ /**************************************************************************************** * 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 IPODWRITEDATABASEJOB_H #define IPODWRITEDATABASEJOB_H #include class IpodCollection; /** * A job designed to call IpodCollection::writeDatabase() in a thread so that main * thread is not blocked with it. It is guaranteed by IpodCollection that is doesn't * destory itself while this job is alive. Memory management of this job is up to * the caller of it. */ class IpodWriteDatabaseJob : public QObject, public ThreadWeaver::Job { Q_OBJECT public: explicit IpodWriteDatabaseJob( IpodCollection *collection ); - void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) Q_DECL_OVERRIDE; + void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) override; 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); protected: - void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; + void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; private: IpodCollection *m_coll; }; #endif // IPODWRITEDATABASEJOB_H diff --git a/src/core-impl/collections/mediadevicecollection/handler/MediaDeviceHandler.h b/src/core-impl/collections/mediadevicecollection/handler/MediaDeviceHandler.h index 56810677d4..e0486359dc 100644 --- a/src/core-impl/collections/mediadevicecollection/handler/MediaDeviceHandler.h +++ b/src/core-impl/collections/mediadevicecollection/handler/MediaDeviceHandler.h @@ -1,507 +1,507 @@ /**************************************************************************************** * Copyright (c) 2009 Alejandro Wainzinger * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) version 3 or * * any later version accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #ifndef MEDIADEVICEHANDLER_H #define MEDIADEVICEHANDLER_H #include "core/meta/Observer.h" #include "core-impl/collections/mediadevicecollection/MediaDeviceMeta.h" #include "core-impl/collections/mediadevicecollection/handler/MediaDeviceHandlerCapability.h" #include "core-impl/collections/mediadevicecollection/handler/capabilities/PlaylistCapability.h" #include "core-impl/collections/mediadevicecollection/handler/capabilities/PodcastCapability.h" #include "core-impl/collections/mediadevicecollection/handler/capabilities/ReadCapability.h" #include "core-impl/collections/mediadevicecollection/handler/capabilities/WriteCapability.h" #include "core-impl/collections/mediadevicecollection/playlist/MediaDevicePlaylist.h" #include "core-impl/collections/mediadevicecollection/playlist/MediaDeviceUserPlaylistProvider.h" #include "core-impl/collections/support/MemoryCollection.h" #include "core-impl/playlists/providers/user/UserPlaylistProvider.h" #include #include #include #include #include class QString; class QMutex; namespace Collections { class MediaDeviceCollection; } namespace Meta { typedef QMultiMap TitleMap; class MEDIADEVICECOLLECTION_EXPORT MetaHandlerCapability { public: virtual ~MetaHandlerCapability() {} virtual bool hasCapabilityInterface( Handler::Capability::Type type ) const; virtual Handler::Capability* createCapabilityInterface( Handler::Capability::Type type ); /** * Retrieves a specialized interface which represents a capability of this * object. * * @returns a pointer to the capability interface if it exists, 0 otherwise */ template CapIface *create() { Handler::Capability::Type type = CapIface::capabilityInterfaceType(); Handler::Capability *iface = createCapabilityInterface(type); return qobject_cast(iface); } /** * Tests if an object provides a given capability interface. * * @returns true if the interface is available, false otherwise */ template bool is() const { return hasCapabilityInterface( CapIface::capabilityInterfaceType() ); } }; /** The MediaDeviceHandler is the backend where all low-level library calls are made. It exists to leave a generic API in the other classes, while allowing for low-level calls to be isolated here. */ class MEDIADEVICECOLLECTION_EXPORT MediaDeviceHandler : public QObject, public Meta::MetaHandlerCapability, public Meta::Observer { Q_OBJECT public: /** * Destructor */ virtual ~MediaDeviceHandler(); // Declare thread as friend class friend class ParseWorkerThread; /** * Begins an attempt to connect to the device, and emits * attemptConnectionDone when it finishes. */ virtual void init() = 0; // collection /** * Checks if the handler successfully connected * to the device. * @return * TRUE if the device was successfully connected to * FALSE if the device was not successfully connected to */ bool succeeded() const // collection { return m_success; } /// Methods provided for CollectionLocation /** * Checks if a device can be written to. * @return * TRUE if the device can be written to * FALSE if the device can not be written to */ virtual bool isWritable() const = 0; /** Given a list of tracks, get URLs for device tracks * of this type of device. If the device needs to * do some work to get URLs (e.g. copy tracks to a * temporary location) the overridden method in * the handler takes care of it, but must emit * gotCopyableUrls when finished. * @param tracks The list of tracks for which to fetch urls */ virtual void getCopyableUrls( const Meta::TrackList &tracks ); /** * Fetches the human-readable name of the device. * This is often called from the Collection since * a library call is needed to get this name. * @return A QString with the name */ virtual QString prettyName() const = 0; /** * Copies a list of tracks to the device. * @param tracklist The list of tracks to copy. */ void copyTrackListToDevice( const Meta::TrackList tracklist ); /** * Removes a list of tracks from the device. * @param tracklist The list of tracks to remove. */ void removeTrackListFromDevice( const Meta::TrackList &tracks ); /** This function is called just before a track in the playlist is to be played, and gives * a chance for e.g. MTP to copy the track to a temporary location, set a playable url, * to emulate the track actually being played off the device * @param track The track that needs to prepare to be played */ virtual void prepareToPlay( Meta::MediaDeviceTrackPtr &track ) { Q_UNUSED( track ) } // called by @param track virtual float usedcapacity(); virtual float totalcapacity(); Playlists::UserPlaylistProvider* provider(); // HACK: Used for device-specific actions, such as initialize for iPod virtual QList collectionActions() { return QList (); } Q_SIGNALS: void gotCopyableUrls( const QMap &urls ); void databaseWritten( bool succeeded ); void deleteTracksDone(); void incrementProgress(); void endProgressOperation( QObject *owner ); void copyTracksDone( bool success ); void removeTracksDone(); /* File I/O Methods */ public Q_SLOTS: /** * Parses the media device's database and creates a Meta::MediaDeviceTrack * for each track in the database. NOTE: only call once per device. */ void parseTracks(); // collection /** * Writes to the device's database if it has one, otherwise * simply calls slotDatabaseWritten to continue the workflow. */ virtual void writeDatabase() { slotDatabaseWritten( true ); } void savePlaylist( const Playlists::MediaDevicePlaylistPtr &playlist, const QString& name ); void renamePlaylist( const Playlists::MediaDevicePlaylistPtr &playlist ); void deletePlaylists( const Playlists::MediaDevicePlaylistList &playlistlist ); bool privateParseTracks(); void copyNextTrackToDevice(); bool privateCopyTrackToDevice( const Meta::TrackPtr& track ); void removeNextTrackFromDevice(); void privateRemoveTrackFromDevice( const Meta::TrackPtr &track ); void slotCopyNextTrackFailed( ThreadWeaver::JobPointer job, const Meta::TrackPtr& track ); void slotCopyNextTrackDone( ThreadWeaver::JobPointer job, const Meta::TrackPtr& track ); protected: /** * Constructor * @param parent the Collection whose handler this is */ MediaDeviceHandler( QObject *parent ); /** * Creates a MediaDeviceTrack based on the latest track struct created as a * result of a copy to the device, and adds it into the collection to reflect * that it has been copied. * @param track The track to add to the collection */ void addMediaDeviceTrackToCollection( Meta::MediaDeviceTrackPtr &track ); /** * Removes the @param track from all the collection's maps to reflect that * it has been removed from the collection * @param track The track to remove from the collection */ void removeMediaDeviceTrackFromCollection( Meta::MediaDeviceTrackPtr &track ); /** * Uses wrapped libGet methods to fill a track with information from device * @param track The track from whose associated struct to get the information * @param destTrack The track that we want to fill with information */ void getBasicMediaDeviceTrackInfo( const Meta::MediaDeviceTrackPtr& track, Meta::MediaDeviceTrackPtr destTrack ); /** * Uses wrapped libSet methods to fill a track struct of the particular library * with information from a Meta::Track * @param srcTrack The track that has the source information * @param destTrack The track whose associated struct we want to fill with information */ void setBasicMediaDeviceTrackInfo( const Meta::TrackPtr &srcTrack, Meta::MediaDeviceTrackPtr destTrack ); Collections::MediaDeviceCollection *m_memColl; ///< Associated collection bool m_success; bool m_copyingthreadsafe; ///< whether or not the handler's method of copying is threadsafe TitleMap m_titlemap; ///< Map of track titles to tracks, used to detect duplicates protected Q_SLOTS: void slotFinalizeTrackCopy( const Meta::TrackPtr & track ); void slotCopyTrackFailed( const Meta::TrackPtr & track ); void slotFinalizeTrackRemove( const Meta::TrackPtr & track ); void slotDatabaseWritten( bool success ); void enqueueNextCopyThread(); void slotDeletingHandler(); private: /** * Pulls out meta information (e.g. artist string) * from track struct, inserts into appropriate map * (e.g. ArtistMap). Sets track's meta info * (e.g. artist string) to that extracted from * track struct's. * @param track - track being written to * @param Map - map where meta information is * associated to appropriate meta pointer * (e.g. QString artist, ArtistPtr ) */ void setupArtistMap( Meta::MediaDeviceTrackPtr track, ArtistMap &artistMap ); void setupAlbumMap( Meta::MediaDeviceTrackPtr track, AlbumMap &albumMap, ArtistMap &artistMap ); void setupGenreMap( Meta::MediaDeviceTrackPtr track, GenreMap &genreMap ); void setupComposerMap( Meta::MediaDeviceTrackPtr track, ComposerMap &composerMap ); void setupYearMap( Meta::MediaDeviceTrackPtr track, YearMap &yearMap ); // Misc. Helper Methods /** * Tries to create read capability in m_rc * @return true if m_rc is valid read capability, false otherwise */ bool setupReadCapability(); /** * Tries to create write capability in m_rc * @return true if m_wc is valid write capability, false otherwise */ bool setupWriteCapability(); /** * @return free space on the device */ float freeSpace(); // Observer Methods /** These methods are called when the metadata of a track has changed. They invoke an MediaDevice DB update */ virtual void metadataChanged( Meta::TrackPtr track ); virtual void metadataChanged( Meta::ArtistPtr artist ); virtual void metadataChanged( Meta::AlbumPtr album ); virtual void metadataChanged( Meta::GenrePtr genre ); virtual void metadataChanged( Meta::ComposerPtr composer ); virtual void metadataChanged( Meta::YearPtr year ); /** * Handler Variables */ Playlists::MediaDeviceUserPlaylistProvider *m_provider; ///< Associated playlist provider bool m_copyFailed; ///< Indicates whether a copy failed or not bool m_isCopying; bool m_isDeleting; Meta::TrackList m_tracksToCopy; ///< List of tracks left to copy Meta::TrackList m_tracksCopying; ///< List of tracks currrently copying Meta::TrackList m_tracksToDelete; ///< List of tracks left to delete int m_numTracksToCopy; ///< The number of tracks left to copy int m_numTracksToRemove; ///< The number of tracks left to remove QMap m_tracksFailed; ///< tracks that failed to copy QHash m_trackSrcDst; ///< points source to destTracks, for completion of addition to collection QMutex m_mutex; ///< A make certain operations atomic when threads are at play // Capability-related variables Handler::PlaylistCapability *m_pc; Handler::PodcastCapability *m_podcastCapability; Handler::ReadCapability *m_rc; Handler::WriteCapability *m_wc; }; /** * The ParseWorkerThread is used to run a full parse of the device's database in * a separate thread. Once done, it informs the Collection it is done */ class ParseWorkerThread : public QObject , public ThreadWeaver::Job { Q_OBJECT public: /** * The constructor. * @param handler The handler */ explicit ParseWorkerThread( MediaDeviceHandler* handler); /** * The destructor. */ virtual ~ParseWorkerThread(); /** * Sees the success variable, which says whether or not the copy completed successfully. * @return Whether or not the copy was successful, i.e. m_success */ - virtual bool success() const; + virtual bool success() const override; 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); private Q_SLOTS: /** * Is called when the job is done successfully, and simply * calls Collection's emitCollectionReady() * @param job The job that was done */ void slotDoneSuccess( ThreadWeaver::JobPointer ); protected: /** * Reimplemented, simply runs the parse method. */ - virtual void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) Q_DECL_OVERRIDE; - void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; + virtual void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) override; + void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; private: bool m_success; ///< Whether or not the parse was successful MediaDeviceHandler *m_handler; ///< The handler }; /** * The CopyWorkerThread is used to run a copy operation on a single track in a separate thread. * Copying is generally done one thread at a time so as to not hurt performance, and because * many copying mechanisms like that of libmtp can only copy one file at a time. Copying * methods that are not threadsafe should not use CopyWorkerThread, and should set the * Handler's m_copyingthreadsafe variable to false in the Handler's constructor. */ class CopyWorkerThread : public QObject, public ThreadWeaver::Job { Q_OBJECT public: /** * The constructor. * @param track The source track to copy from * @param handler The handler */ CopyWorkerThread( const Meta::TrackPtr &track, MediaDeviceHandler* handler ); /** * The destructor. */ virtual ~CopyWorkerThread(); /** * Sets the success variable, which says whether or not the copy completed successfully. * @return Whether or not the copy was successful, i.e. m_success */ - virtual bool success() const; + virtual bool success() const override; Q_SIGNALS: /** * Is emitted when the job is done successfully * @param job The job that was done * @param track The source track used for the copy */ void copyTrackDone( ThreadWeaver::JobPointer, const Meta::TrackPtr& track ); /** * Is emitted when the job is done and has failed * @param job The job that was done * @param track The source track used for the copy */ void copyTrackFailed( ThreadWeaver::JobPointer, const Meta::TrackPtr& track ); /** 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); private Q_SLOTS: /** * Is called when the job is done successfully, and simply * emits copyTrackDone * @param job The job that was done */ void slotDoneSuccess( ThreadWeaver::JobPointer ); /** * Is called when the job is done and failed, and simply * emits copyTrackFailed * @param job The job that was done */ void slotDoneFailed( ThreadWeaver::JobPointer ); protected: /** * Reimplemented, simply runs the copy track method. */ - virtual void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) Q_DECL_OVERRIDE; - void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; + virtual void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) override; + void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; private: bool m_success; ///< Whether or not the copy was successful Meta::TrackPtr m_track; ///< The source track to copy from MediaDeviceHandler *m_handler; ///< The handler }; } #endif diff --git a/src/core-impl/collections/mediadevicecollection/playlist/MediaDeviceUserPlaylistProvider.cpp b/src/core-impl/collections/mediadevicecollection/playlist/MediaDeviceUserPlaylistProvider.cpp index 4bd93376ea..d9d9278496 100644 --- a/src/core-impl/collections/mediadevicecollection/playlist/MediaDeviceUserPlaylistProvider.cpp +++ b/src/core-impl/collections/mediadevicecollection/playlist/MediaDeviceUserPlaylistProvider.cpp @@ -1,162 +1,162 @@ /**************************************************************************************** * Copyright (c) 2008 Nikolaj Hald Nielsen * * Copyright (c) 2008 Bart Cerneels * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #include "MediaDeviceUserPlaylistProvider.h" #include "SvgHandler.h" #include "browsers/playlistbrowser/UserPlaylistModel.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include "core-impl/collections/mediadevicecollection/MediaDeviceCollection.h" #include "core-impl/collections/support/CollectionManager.h" #include "core-impl/playlists/types/file/PlaylistFileSupport.h" #include "core-impl/playlists/types/file/m3u/M3UPlaylist.h" #include "core-impl/playlists/types/file/pls/PLSPlaylist.h" #include "core-impl/playlists/types/file/xspf/XSPFPlaylist.h" #include "playlistmanager/PlaylistManager.h" #include #include #include #include -static const int USERPLAYLIST_DB_VERSION = 2; +// static const int USERPLAYLIST_DB_VERSION = 2; static const QString key("AMAROK_USERPLAYLIST"); namespace Playlists { MediaDeviceUserPlaylistProvider::MediaDeviceUserPlaylistProvider( Collections::MediaDeviceCollection *collection ) : Playlists::UserPlaylistProvider() , m_collection( collection ) { DEBUG_BLOCK // checkTables(); // m_root = Playlists::MediaDevicePlaylistGroupPtr( new Playlists::MediaDevicePlaylistGroup( "", // Playlists::MediaDevicePlaylistGroupPtr() ) ); // The::playlistManager()->addProvider( this, category() ); } MediaDeviceUserPlaylistProvider::~MediaDeviceUserPlaylistProvider() { DEBUG_BLOCK // foreach( Playlists::MediaDevicePlaylistPtr playlist, m_playlists ) // { // playlist->saveToDb( true ); // } m_playlists.clear(); // emit updated(); // The::playlistManager()->removeProvider( this ); } Playlists::PlaylistList MediaDeviceUserPlaylistProvider::playlists() { DEBUG_BLOCK Playlists::PlaylistList playlists; foreach( Playlists::MediaDevicePlaylistPtr mediadevicePlaylist, m_playlists ) { playlists << Playlists::PlaylistPtr::staticCast( mediadevicePlaylist ); } return playlists; } Playlists::PlaylistPtr MediaDeviceUserPlaylistProvider::save( const Meta::TrackList &tracks ) { DEBUG_BLOCK // This provider can only save it's own tracks for now, filter out all the others. Meta::TrackList filteredTracks; foreach( const Meta::TrackPtr track, tracks ) if( track->collection() == m_collection ) filteredTracks << track; return save( filteredTracks, QDateTime::currentDateTime().toString( "ddd MMMM d yy hh-mm" ) ); } Playlists::PlaylistPtr MediaDeviceUserPlaylistProvider::save( const Meta::TrackList &tracks, const QString& name ) { DEBUG_BLOCK debug() << "saving " << tracks.count() << " tracks to device with name" << name; // NOTE: the playlist constructor tells the handler to make the playlist, save to db etc. Playlists::MediaDevicePlaylistPtr pl = Playlists::MediaDevicePlaylistPtr( new Playlists::MediaDevicePlaylist( name, tracks ) ); //pl = 0; emit playlistSaved( pl, name ); // inform handler of new playlist addMediaDevicePlaylist( pl ); return Playlists::PlaylistPtr::dynamicCast( pl ); } void MediaDeviceUserPlaylistProvider::renamePlaylist( Playlists::PlaylistPtr playlist, const QString &newName ) { DEBUG_BLOCK Playlists::MediaDevicePlaylistPtr pl = Playlists::MediaDevicePlaylistPtr::staticCast( playlist ); if( pl ) { debug() << "Setting name of playlist"; pl->setName( newName ); emit playlistRenamed( pl ); } } bool MediaDeviceUserPlaylistProvider::deletePlaylists( const Playlists::PlaylistList &playlistlist ) { Playlists::MediaDevicePlaylistList pllist; foreach( Playlists::PlaylistPtr playlist, playlistlist ) { Playlists::MediaDevicePlaylistPtr pl = Playlists::MediaDevicePlaylistPtr::staticCast( playlist ); if( pl ) { debug() << "Deleting playlist: " << pl->name(); removePlaylist( pl ); pllist << pl; } } emit playlistsDeleted( pllist ); return true; } void MediaDeviceUserPlaylistProvider::addMediaDevicePlaylist( Playlists::MediaDevicePlaylistPtr &playlist ) { m_playlists << playlist; emit updated(); } void MediaDeviceUserPlaylistProvider::removePlaylist( Playlists::MediaDevicePlaylistPtr &playlist ) { m_playlists.removeOne( playlist ); emit updated(); } } //namespace Playlists diff --git a/src/core-impl/collections/mtpcollection/handler/MtpHandler.h b/src/core-impl/collections/mtpcollection/handler/MtpHandler.h index e67e5d368f..bc219d356b 100644 --- a/src/core-impl/collections/mtpcollection/handler/MtpHandler.h +++ b/src/core-impl/collections/mtpcollection/handler/MtpHandler.h @@ -1,288 +1,288 @@ /**************************************************************************************** * Copyright (c) 2006 Andy Kelk * * Copyright (c) 2008 Alejandro Wainzinger * * Copyright (c) 2009 Mark Kretschmann * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) version 3 or * * any later version accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #ifndef MTPHANDLER_H #define MTPHANDLER_H #include #include "MtpPlaylistCapability.h" #include "MtpReadCapability.h" #include "MtpWriteCapability.h" #include "MediaDeviceMeta.h" #include "MediaDeviceHandler.h" #include #include #include #include #include #include #include #include #include #include class QString; class QMutex; class QStringList; namespace Collections { class MtpCollection; } namespace Meta { typedef QMultiMap TitleMap; class WorkerThread; /* The libmtp backend for all Mtp calls */ class MtpHandler : public MediaDeviceHandler { Q_OBJECT public: explicit MtpHandler( Collections::MtpCollection *mc ); virtual ~MtpHandler(); friend class WorkerThread; virtual void init(); // collection virtual bool isWritable() const; virtual void getCopyableUrls( const Meta::TrackList &tracks ); virtual QString prettyName() const; virtual void prepareToPlay( Meta::MediaDeviceTrackPtr &track ); /// Capability-related methods virtual bool hasCapabilityInterface( Handler::Capability::Type type ) const; virtual Handler::Capability* createCapabilityInterface( Handler::Capability::Type type ); friend class Handler::MtpPlaylistCapability; friend class Handler::MtpReadCapability; friend class Handler::MtpWriteCapability; protected: /* Parsing of Tracks on Device */ virtual void prepareToParseTracks(); virtual bool isEndOfParseTracksList(); virtual void prepareToParseNextTrack(); virtual void nextTrackToParse(); virtual void setAssociateTrack( const Meta::MediaDeviceTrackPtr track ); virtual void prepareToParsePlaylists(); virtual bool isEndOfParsePlaylistsList(); virtual void prepareToParseNextPlaylist(); virtual void nextPlaylistToParse(); virtual bool shouldNotParseNextPlaylist(); virtual void prepareToParsePlaylistTracks(); virtual bool isEndOfParsePlaylist(); virtual void prepareToParseNextPlaylistTrack(); virtual void nextPlaylistTrackToParse(); virtual QStringList supportedFormats(); virtual void findPathToCopy( const Meta::TrackPtr &srcTrack, const Meta::MediaDeviceTrackPtr &destTrack ); virtual bool libCopyTrack( const Meta::TrackPtr &srcTrack, Meta::MediaDeviceTrackPtr &destTrack ); virtual bool libDeleteTrackFile( const Meta::MediaDeviceTrackPtr &track ); virtual void libCreateTrack( const Meta::MediaDeviceTrackPtr &track ); virtual void libDeleteTrack( const Meta::MediaDeviceTrackPtr &track ); virtual Meta::MediaDeviceTrackPtr libGetTrackPtrForTrackStruct(); virtual QString libGetPlaylistName(); virtual void setAssociatePlaylist( const Playlists::MediaDevicePlaylistPtr &playlist ); virtual void libSavePlaylist( const Playlists::MediaDevicePlaylistPtr &playlist, const QString& name ); virtual void deletePlaylist( const Playlists::MediaDevicePlaylistPtr &playlist ); virtual void renamePlaylist( const Playlists::MediaDevicePlaylistPtr &playlist ); virtual void addTrackInDB( const Meta::MediaDeviceTrackPtr &track ) { Q_UNUSED( track ) } virtual void removeTrackFromDB( const Meta::MediaDeviceTrackPtr &track ) { Q_UNUSED( track ) } virtual void setDatabaseChanged(); virtual QString libGetTitle( const Meta::MediaDeviceTrackPtr &track ); virtual QString libGetAlbum( const Meta::MediaDeviceTrackPtr &track ); virtual QString libGetArtist( const Meta::MediaDeviceTrackPtr &track ); virtual QString libGetAlbumArtist( const Meta::MediaDeviceTrackPtr &track ); virtual QString libGetComposer( const Meta::MediaDeviceTrackPtr &track ); virtual QString libGetGenre( const Meta::MediaDeviceTrackPtr &track ); virtual int libGetYear( const Meta::MediaDeviceTrackPtr &track ); virtual qint64 libGetLength( const Meta::MediaDeviceTrackPtr &track ); virtual int libGetTrackNumber( const Meta::MediaDeviceTrackPtr &track ); virtual QString libGetComment( const Meta::MediaDeviceTrackPtr &track ); virtual int libGetDiscNumber( const Meta::MediaDeviceTrackPtr &track ); virtual int libGetBitrate( const Meta::MediaDeviceTrackPtr &track ); virtual int libGetSamplerate( const Meta::MediaDeviceTrackPtr &track ); virtual qreal libGetBpm( const Meta::MediaDeviceTrackPtr &track ); virtual int libGetFileSize( const Meta::MediaDeviceTrackPtr &track ); virtual int libGetPlayCount( const Meta::MediaDeviceTrackPtr &track ); virtual QDateTime libGetLastPlayed( const Meta::MediaDeviceTrackPtr &track ); virtual int libGetRating( const Meta::MediaDeviceTrackPtr &track ) ; virtual QString libGetType( const Meta::MediaDeviceTrackPtr &track ); virtual QUrl libGetPlayableUrl( const Meta::MediaDeviceTrackPtr &track ); virtual float usedCapacity() const; virtual float totalCapacity() const; virtual void libSetTitle( Meta::MediaDeviceTrackPtr &track, const QString& title ); virtual void libSetAlbum( Meta::MediaDeviceTrackPtr &track, const QString& album ); virtual void libSetArtist( Meta::MediaDeviceTrackPtr &track, const QString& artist ); virtual void libSetAlbumArtist( Meta::MediaDeviceTrackPtr &track, const QString& albumArtist ); virtual void libSetComposer( Meta::MediaDeviceTrackPtr &track, const QString& composer ); virtual void libSetGenre( Meta::MediaDeviceTrackPtr &track, const QString& genre ); virtual void libSetYear( Meta::MediaDeviceTrackPtr &track, const QString& year ); virtual void libSetLength( Meta::MediaDeviceTrackPtr &track, int length ); virtual void libSetTrackNumber( Meta::MediaDeviceTrackPtr &track, int tracknum ); virtual void libSetComment( Meta::MediaDeviceTrackPtr &track, const QString& comment ); virtual void libSetDiscNumber( Meta::MediaDeviceTrackPtr &track, int discnum ); virtual void libSetBitrate( Meta::MediaDeviceTrackPtr &track, int bitrate ); virtual void libSetSamplerate( Meta::MediaDeviceTrackPtr &track, int samplerate ); virtual void libSetBpm( Meta::MediaDeviceTrackPtr &track, qreal bpm ); virtual void libSetFileSize( Meta::MediaDeviceTrackPtr &track, int filesize ); virtual void libSetPlayCount( Meta::MediaDeviceTrackPtr &track, int playcount ); virtual void libSetLastPlayed( Meta::MediaDeviceTrackPtr &track, const QDateTime &lastplayed ); virtual void libSetRating( Meta::MediaDeviceTrackPtr &track, int rating ) ; virtual void libSetType( Meta::MediaDeviceTrackPtr &track, const QString& type ); virtual void libSetPlayableUrl( Meta::MediaDeviceTrackPtr &destTrack, const Meta::TrackPtr &srcTrack ); virtual void prepareToCopy() {} virtual void prepareToDelete() {} /// libmtp-specific private Q_SLOTS: void slotDeviceMatchSucceeded( ThreadWeaver::JobPointer job ); void slotDeviceMatchFailed( ThreadWeaver::JobPointer job ); private: bool iterateRawDevices( int numrawdevices, LIBMTP_raw_device_t* rawdevices ); void getDeviceInfo(); void terminate(); int getTrackToFile( const uint32_t id, const QString & filename ); // Some internal stuff that must be public due to libmtp being in C static int progressCallback( uint64_t const sent, uint64_t const total, void const * const data ); // file-copying related functions uint32_t checkFolderStructure( const Meta::TrackPtr track, bool create ); uint32_t getDefaultParentId( void ); uint32_t folderNameToID( char *name, LIBMTP_folder_t *folderlist ); uint32_t subfolderNameToID( const char *name, LIBMTP_folder_t *folderlist, uint32_t parent_id ); uint32_t createFolder( const char *name, uint32_t parent_id ); void updateFolders( void ); QString setTempFile( Meta::MediaDeviceTrackPtr &track, const QString &format ); virtual void updateTrack( Meta::MediaDeviceTrackPtr &track ); // mtp database LIBMTP_mtpdevice_t *m_device; float m_capacity; QMap mtpFileTypes; uint32_t m_default_parent_folder; LIBMTP_folder_t *m_folders; QString m_folderStructure; QString m_format; QString m_name; QStringList m_supportedFiles; QMutex m_critical_mutex; // KIO-related Vars (to be moved elsewhere eventually) bool m_isCanceled; bool m_wait; bool m_dbChanged; LIBMTP_track_t* m_currentTrackList; LIBMTP_track_t* m_currentTrack; LIBMTP_playlist_t* m_currentPlaylistList; LIBMTP_playlist_t* m_currentPlaylist; QHash m_mtpPlaylisthash; uint32_t m_trackcounter; // Hash that associates an LIBMTP_track_t* to every Track* QHash m_mtpTrackHash; // Keeps track of which tracks have been copied/cached for playing QHash m_cachedTracks; // Maps id's to tracks QHash m_idTrackHash; // parentid calculated for new track copied to device uint32_t m_copyParentId; // Used as temporary location for copying files from mtp QTemporaryDir *m_tempDir; }; class WorkerThread : public QObject, public ThreadWeaver::Job { Q_OBJECT public: WorkerThread( int numrawdevices, LIBMTP_raw_device_t* rawdevices, MtpHandler* handler ); virtual ~WorkerThread(); - virtual bool success() const Q_DECL_OVERRIDE; + virtual bool success() const override; protected: - virtual void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) Q_DECL_OVERRIDE; - void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; + virtual void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) override; + void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; 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); private: bool m_success; int m_numrawdevices; LIBMTP_raw_device_t* m_rawdevices; MtpHandler *m_handler; }; } #endif diff --git a/src/core-impl/collections/nepomukcollection/NepomukInquirer.h b/src/core-impl/collections/nepomukcollection/NepomukInquirer.h index ea7f601f44..90ece0e76b 100644 --- a/src/core-impl/collections/nepomukcollection/NepomukInquirer.h +++ b/src/core-impl/collections/nepomukcollection/NepomukInquirer.h @@ -1,65 +1,65 @@ /**************************************************************************************** * Copyright (c) 2013 Edward Toroshchin * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #ifndef AMAROK_COLLECTION_NEPOMUKINQUIRER_H #define AMAROK_COLLECTION_NEPOMUKINQUIRER_H #include #include #include namespace Collections { class NepomukParser; class NepomukInquirerPrivate; /** * A ThreadWeaver Job that runs the given SPARQL query on a Nepomuk's * database and passes the results to a given parser. */ class NepomukInquirer: public QObject, public ThreadWeaver::Job { Q_OBJECT public: NepomukInquirer( QString query, std::auto_ptr parser ); ~NepomukInquirer(); 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); protected: - void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) Q_DECL_OVERRIDE; + void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) override; private: QString m_query; NepomukParser *m_parser; }; } #endif // AMAROK_COLLECTION_NEPOMUKINQUIRER_H diff --git a/src/core-impl/collections/playdarcollection/PlaydarCollection.cpp b/src/core-impl/collections/playdarcollection/PlaydarCollection.cpp index 328618db01..d519957dca 100644 --- a/src/core-impl/collections/playdarcollection/PlaydarCollection.cpp +++ b/src/core-impl/collections/playdarcollection/PlaydarCollection.cpp @@ -1,373 +1,373 @@ /**************************************************************************************** * Copyright (c) 2010 Andrew Coder * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #define DEBUG_PREFIX "PlaydarCollection" #include "PlaydarCollection.h" #include "core/collections/Collection.h" #include "core-impl/collections/support/MemoryCollection.h" #include "core-impl/collections/support/MemoryQueryMaker.h" #include "core-impl/collections/support/CollectionManager.h" #include "core-impl/meta/proxy/MetaProxy.h" #include "PlaydarQueryMaker.h" #include "support/Controller.h" #include "support/ProxyResolver.h" #include "core/support/Debug.h" #include #include #include #include #include #include namespace Collections { PlaydarCollectionFactory::PlaydarCollectionFactory() : CollectionFactory() , m_controller( 0 ) , m_collectionIsManaged( false ) { DEBUG_BLOCK } PlaydarCollectionFactory::~PlaydarCollectionFactory() { DEBUG_BLOCK delete m_collection.data(); delete m_controller; } void PlaydarCollectionFactory::init() { DEBUG_BLOCK - m_controller = new Playdar::Controller( this ); + m_controller = new Playdar::Controller; connect( m_controller, &Playdar::Controller::playdarReady, this, &PlaydarCollectionFactory::playdarReady ); connect( m_controller, &Playdar::Controller::playdarError, this, &PlaydarCollectionFactory::slotPlaydarError ); checkStatus(); m_collection = new PlaydarCollection; connect( m_collection, &PlaydarCollection::remove, this, &PlaydarCollectionFactory::collectionRemoved ); CollectionManager::instance()->addTrackProvider( m_collection ); m_initialized = true; } void PlaydarCollectionFactory::checkStatus() { m_controller->status(); } void PlaydarCollectionFactory::playdarReady() { DEBUG_BLOCK if( !m_collection ) { m_collection = new PlaydarCollection(); connect( m_collection, &PlaydarCollection::remove, this, &PlaydarCollectionFactory::collectionRemoved ); } if( !m_collectionIsManaged ) { m_collectionIsManaged = true; emit newCollection( m_collection.data() ); } } void PlaydarCollectionFactory::slotPlaydarError( Playdar::Controller::ErrorState error ) { // DEBUG_BLOCK if( error == Playdar::Controller::ErrorState( 1 ) ) { if( m_collection && !m_collectionIsManaged ) CollectionManager::instance()->removeTrackProvider( m_collection.data() ); QTimer::singleShot( 10 * 60 * 1000, this, &PlaydarCollectionFactory::checkStatus ); } } void PlaydarCollectionFactory::collectionRemoved() { DEBUG_BLOCK m_collectionIsManaged = false; QTimer::singleShot( 10000, this, &PlaydarCollectionFactory::checkStatus ); } PlaydarCollection::PlaydarCollection() : m_collectionId( i18n( "Playdar Collection" ) ) , m_memoryCollection( new MemoryCollection ) { DEBUG_BLOCK } PlaydarCollection::~PlaydarCollection() { DEBUG_BLOCK } QueryMaker* PlaydarCollection::queryMaker() { DEBUG_BLOCK PlaydarQueryMaker *freshQueryMaker = new PlaydarQueryMaker( this ); connect( freshQueryMaker, &PlaydarQueryMaker::playdarError, this, &PlaydarCollection::slotPlaydarError ); return freshQueryMaker; } Playlists::UserPlaylistProvider* PlaydarCollection::userPlaylistProvider() { DEBUG_BLOCK return 0; } QString PlaydarCollection::uidUrlProtocol() const { return QString( "playdar" ); } QString PlaydarCollection::collectionId() const { return m_collectionId; } QString PlaydarCollection::prettyName() const { return collectionId(); } QIcon PlaydarCollection::icon() const { return QIcon::fromTheme( "network-server" ); } bool PlaydarCollection::isWritable() const { DEBUG_BLOCK return false; } bool PlaydarCollection::isOrganizable() const { DEBUG_BLOCK return false; } bool PlaydarCollection::possiblyContainsTrack( const QUrl &url ) const { DEBUG_BLOCK QUrlQuery query( url ); if( url.scheme() == uidUrlProtocol() && query.hasQueryItem( QString( "artist" ) ) && query.hasQueryItem( QString( "album" ) ) && query.hasQueryItem( QString( "title" ) ) ) return true; else return false; } Meta::TrackPtr PlaydarCollection::trackForUrl( const QUrl &url ) { DEBUG_BLOCK m_memoryCollection->acquireReadLock(); if( m_memoryCollection->trackMap().contains( url.url() ) ) { Meta::TrackPtr track = m_memoryCollection->trackMap().value( url.url() ); m_memoryCollection->releaseLock(); return track; } else { m_memoryCollection->releaseLock(); MetaProxy::TrackPtr proxyTrack( new MetaProxy::Track( url ) ); proxyTrack->setArtist( QUrlQuery(url).queryItemValue( "artist" ) ); proxyTrack->setAlbum( QUrlQuery(url).queryItemValue( "album" ) ); proxyTrack->setTitle( QUrlQuery(url).queryItemValue( "title" ) ); Playdar::ProxyResolver *proxyResolver = new Playdar::ProxyResolver( this, url, proxyTrack ); connect( proxyResolver, &Playdar::ProxyResolver::playdarError, this, &PlaydarCollection::slotPlaydarError ); return Meta::TrackPtr::staticCast( proxyTrack ); } } bool PlaydarCollection::hasCapabilityInterface( Capabilities::Capability::Type type ) const { //TODO: Make this work once capabilities are set. Q_UNUSED( type ); return false; } Capabilities::Capability* PlaydarCollection::createCapabilityInterface( Capabilities::Capability::Type type ) { //TODO: Make this work once capabilities are set. Q_UNUSED( type ); return 0; } void PlaydarCollection::addNewTrack( Meta::PlaydarTrackPtr track ) { DEBUG_BLOCK m_memoryCollection->acquireReadLock(); if( !m_memoryCollection->trackMap().contains( track->uidUrl() ) ) { m_memoryCollection->releaseLock(); m_memoryCollection->acquireWriteLock(); Meta::PlaydarArtistPtr artistPtr; if( m_memoryCollection->artistMap().contains( track->artist()->name() ) ) { Meta::ArtistPtr artist = m_memoryCollection->artistMap().value( track->artist()->name() ); artistPtr = Meta::PlaydarArtistPtr::staticCast( artist ); } else { artistPtr = track->playdarArtist(); Meta::ArtistPtr artist = Meta::ArtistPtr::staticCast( artistPtr ); m_memoryCollection->addArtist( artist ); } artistPtr->addTrack( track ); track->setArtist( artistPtr ); Meta::PlaydarAlbumPtr albumPtr; if( m_memoryCollection->albumMap().contains( track->album()->name(), artistPtr->name() ) ) { Meta::AlbumPtr album = m_memoryCollection->albumMap().value( track->album()->name(), artistPtr->name() ); albumPtr = Meta::PlaydarAlbumPtr::staticCast( album ); } else { albumPtr = track->playdarAlbum(); albumPtr->setAlbumArtist( artistPtr ); artistPtr->addAlbum( albumPtr ); Meta::AlbumPtr album = Meta::AlbumPtr::staticCast( albumPtr ); m_memoryCollection->addAlbum( album ); } albumPtr->addTrack( track ); track->setAlbum( albumPtr ); Meta::PlaydarGenrePtr genrePtr; if( m_memoryCollection->genreMap().contains( track->genre()->name() ) ) { Meta::GenrePtr genre = m_memoryCollection->genreMap().value( track->genre()->name() ); genrePtr = Meta::PlaydarGenrePtr::staticCast( genre ); } else { genrePtr = track->playdarGenre(); Meta::GenrePtr genre = Meta::GenrePtr::staticCast( genrePtr ); m_memoryCollection->addGenre( genre ); } genrePtr->addTrack( track ); track->setGenre( genrePtr ); Meta::PlaydarComposerPtr composerPtr; if( m_memoryCollection->composerMap().contains( track->composer()->name() ) ) { Meta::ComposerPtr composer = m_memoryCollection->composerMap().value( track->composer()->name() ); composerPtr = Meta::PlaydarComposerPtr::staticCast( composer ); } else { composerPtr = track->playdarComposer(); Meta::ComposerPtr composer = Meta::ComposerPtr::staticCast( composerPtr ); m_memoryCollection->addComposer( composer ); } composerPtr->addTrack( track ); track->setComposer( composerPtr ); Meta::PlaydarYearPtr yearPtr; if( m_memoryCollection->yearMap().contains( track->year()->year() ) ) { Meta::YearPtr year = m_memoryCollection->yearMap().value( track->year()->year() ); yearPtr = Meta::PlaydarYearPtr::staticCast( year ); } else { yearPtr = track->playdarYear(); Meta::YearPtr year = Meta::YearPtr::staticCast( yearPtr ); m_memoryCollection->addYear( year ); } yearPtr->addTrack( track ); track->setYear( yearPtr ); m_memoryCollection->addTrack( Meta::TrackPtr::staticCast( track ) ); foreach( Meta::PlaydarLabelPtr label, track->playdarLabels() ) { m_memoryCollection->addLabelToTrack( Meta::LabelPtr::staticCast( label ), Meta::TrackPtr::staticCast( track ) ); } m_memoryCollection->releaseLock(); emit updated(); } else m_memoryCollection->releaseLock(); } QSharedPointer< MemoryCollection > PlaydarCollection::memoryCollection() { return m_memoryCollection; } void PlaydarCollection::slotPlaydarError( Playdar::Controller::ErrorState error ) { if( error == Playdar::Controller::ErrorState( 1 ) ) emit remove(); } } diff --git a/src/core-impl/collections/support/MemoryQueryMaker.h b/src/core-impl/collections/support/MemoryQueryMaker.h index b8498776df..de2daa47e1 100644 --- a/src/core-impl/collections/support/MemoryQueryMaker.h +++ b/src/core-impl/collections/support/MemoryQueryMaker.h @@ -1,94 +1,94 @@ /**************************************************************************************** * 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 . * ****************************************************************************************/ #ifndef MEMORYQUERYMAKER_H #define MEMORYQUERYMAKER_H #include "amarok_export.h" #include "MemoryCollection.h" #include "core/collections/QueryMaker.h" #include #include namespace ThreadWeaver { class Job; } namespace Collections { class AMAROK_EXPORT MemoryQueryMaker : public QueryMaker { Q_OBJECT public: /** * Creates a new MemoryQueryMaker that will query a memory collection. * This class implements the QueryMaker interface and can be used as a generic * query maker for all collections that use MemoryCollection. * @param mc the MemoryCollection instance that the query should be run on. * @param collectionId the collectionid that has to be emitted by this querymaker. */ MemoryQueryMaker( QWeakPointer mc, const QString &collectionId ); virtual ~MemoryQueryMaker(); - virtual void run() Q_DECL_OVERRIDE; - virtual void abortQuery(); + virtual void run() override; + virtual void abortQuery() override; - virtual QueryMaker* setQueryType( QueryType type ); + virtual QueryMaker* setQueryType( QueryType type ) override; - virtual QueryMaker* addReturnValue( qint64 value ); - virtual QueryMaker* addReturnFunction( ReturnFunction function, qint64 value ); - virtual QueryMaker* orderBy( qint64 value, bool descending = false ); + virtual QueryMaker* addReturnValue( qint64 value ) override; + virtual QueryMaker* addReturnFunction( ReturnFunction function, qint64 value ) override; + virtual QueryMaker* orderBy( qint64 value, bool descending = false ) override; - virtual QueryMaker* addMatch( const Meta::TrackPtr &track ); - virtual QueryMaker* addMatch( const Meta::ArtistPtr &artist, ArtistMatchBehaviour behaviour = TrackArtists ); - virtual QueryMaker* addMatch( const Meta::AlbumPtr &album ); - virtual QueryMaker* addMatch( const Meta::ComposerPtr &composer ); - virtual QueryMaker* addMatch( const Meta::GenrePtr &genre ); - virtual QueryMaker* addMatch( const Meta::YearPtr &year ); - virtual QueryMaker* addMatch( const Meta::LabelPtr &label ); + virtual QueryMaker* addMatch( const Meta::TrackPtr &track ) override; + virtual QueryMaker* addMatch( const Meta::ArtistPtr &artist, ArtistMatchBehaviour behaviour = TrackArtists ) override; + virtual QueryMaker* addMatch( const Meta::AlbumPtr &album ) override; + virtual QueryMaker* addMatch( const Meta::ComposerPtr &composer ) override; + virtual QueryMaker* addMatch( const Meta::GenrePtr &genre ) override; + virtual QueryMaker* addMatch( const Meta::YearPtr &year ) override; + virtual QueryMaker* addMatch( const Meta::LabelPtr &label ) override; - virtual QueryMaker* addFilter( qint64 value, const QString &filter, bool matchBegin, bool matchEnd ); - virtual QueryMaker* excludeFilter( qint64 value, const QString &filter, bool matchBegin, bool matchEnd ); + virtual QueryMaker* addFilter( qint64 value, const QString &filter, bool matchBegin, bool matchEnd ) override; + virtual QueryMaker* excludeFilter( qint64 value, const QString &filter, bool matchBegin, bool matchEnd ) override; - virtual QueryMaker* addNumberFilter( qint64 value, qint64 filter, NumberComparison compare ); - virtual QueryMaker* excludeNumberFilter( qint64 value, qint64 filter, NumberComparison compare ); + virtual QueryMaker* addNumberFilter( qint64 value, qint64 filter, NumberComparison compare ) override; + virtual QueryMaker* excludeNumberFilter( qint64 value, qint64 filter, NumberComparison compare ) override; - virtual QueryMaker* limitMaxResultSize( int size ); + virtual QueryMaker* limitMaxResultSize( int size ) override; - virtual QueryMaker* beginAnd(); - virtual QueryMaker* beginOr(); - virtual QueryMaker* endAndOr(); + virtual QueryMaker* beginAnd() override; + virtual QueryMaker* beginOr() override; + virtual QueryMaker* endAndOr() override; - virtual QueryMaker* setAlbumQueryMode( AlbumQueryMode mode ); - virtual QueryMaker* setLabelQueryMode( LabelQueryMode mode ); + virtual QueryMaker* setAlbumQueryMode( AlbumQueryMode mode ) override; + virtual QueryMaker* setLabelQueryMode( LabelQueryMode mode ) override; private Q_SLOTS: void done( ThreadWeaver::JobPointer job ); protected: QWeakPointer m_collection; struct Private; Private * const d; }; } //namespace Collections #endif diff --git a/src/core-impl/collections/support/jobs/WriteTagsJob.h b/src/core-impl/collections/support/jobs/WriteTagsJob.h index 1f72aeda23..4a3c12a3e4 100644 --- a/src/core-impl/collections/support/jobs/WriteTagsJob.h +++ b/src/core-impl/collections/support/jobs/WriteTagsJob.h @@ -1,63 +1,63 @@ /**************************************************************************************** * 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 WRITETAGSJOB_H #define WRITETAGSJOB_H #include "amarok_export.h" #include "MetaValues.h" #include /** * Calls Meta::Tag::writeTags( path, changedFields ) in a thread so that main thread * is not blocked with IO. writeTags() respects AmarokConfig::writeBackStatistics, * AmarokConfig::writeBack(). * * If @param changes contains Meta::valImage, writes back image too, respecting * AmarokConfig::writeBackCover(). * * The caller is responsible to delete this job after use, perhaps by connecting its * done() signal to its deleteLater() slot. */ class AMAROK_EXPORT WriteTagsJob :public QObject, public ThreadWeaver::Job { Q_OBJECT public: WriteTagsJob( const QString &path, const Meta::FieldHash &changes, bool respectConfig = true ); - virtual void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) Q_DECL_OVERRIDE; + virtual void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) override; protected: - void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; + void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; 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); private: const QString m_path; const Meta::FieldHash m_changes; const bool m_respectConfig; }; #endif // WRITETAGSJOB_H diff --git a/src/core-impl/meta/proxy/MetaProxyWorker.h b/src/core-impl/meta/proxy/MetaProxyWorker.h index 90722d8737..f8fa0e8d87 100644 --- a/src/core-impl/meta/proxy/MetaProxyWorker.h +++ b/src/core-impl/meta/proxy/MetaProxyWorker.h @@ -1,73 +1,73 @@ /**************************************************************************************** * Copyright (c) 2012 Bart Cerneels * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #ifndef METAPROXY_METAPROXYWORKER_H #define METAPROXY_METAPROXYWORKER_H #include "core/collections/Collection.h" #include #include #include namespace MetaProxy { /** * Worker to get real track for MetaProxy::Track. Worker deletes itself somewhere * after emitting finishedLookup(). */ class Worker : public QObject, public ThreadWeaver::Job { Q_OBJECT public: /** * If @param provider is null (the default), all providers registered to * CollectionManager are used and a watch for new providers is used. * Otherwise the lookup happes just in @param provider and is one-shot. */ explicit Worker( const QUrl &url, Collections::TrackProvider *provider = 0 ); //TrackForUrlWorker virtual methods - virtual void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) Q_DECL_OVERRIDE; + virtual void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) override; protected: - void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; + void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; Q_SIGNALS: void finishedLookup( Meta::TrackPtr track ); /** 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); private Q_SLOTS: void slotNewTrackProvider( Collections::TrackProvider *newTrackProvider ); void slotNewCollection( Collections::Collection *newCollection ); private: QUrl m_url; Collections::TrackProvider *m_provider; int m_stepsDoneReceived; }; } // namespace MetaProxy #endif // METAPROXY_METAPROXYWORKER_H diff --git a/src/core-impl/meta/timecode/TimecodeMeta.cpp b/src/core-impl/meta/timecode/TimecodeMeta.cpp index f8fa55ad6d..351f481da7 100644 --- a/src/core-impl/meta/timecode/TimecodeMeta.cpp +++ b/src/core-impl/meta/timecode/TimecodeMeta.cpp @@ -1,625 +1,625 @@ /**************************************************************************************** * Copyright (c) 2009 Nikolaj Hald Nielsen * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #include "core-impl/meta/timecode/TimecodeMeta.h" #include "core/support/Debug.h" #include "covermanager/CoverCache.h" #include "covermanager/CoverFetchingActions.h" #include "covermanager/CoverFetcher.h" #include "core/capabilities/ActionsCapability.h" #include "core/capabilities/Capability.h" #include "core/capabilities/BoundedPlaybackCapability.h" #include "core-impl/capabilities/AlbumActionsCapability.h" #include "core-impl/capabilities/timecode/TimecodeBoundedPlaybackCapability.h" #include using namespace Meta; using namespace Capabilities; ////////////////// TRACK ////////////////// TimecodeTrack::TimecodeTrack( const QString &name, const QUrl &url, qint64 start, qint64 end ) : m_name( name ) , m_start( start ) , m_end( end ) , m_length( end - start ) , m_bpm( -1.0 ) , m_trackNumber( 0 ) , m_discNumber( 0 ) , m_comment( QString() ) , m_playableUrl( url ) , m_updatedFields( 0 ) { m_displayUrl = url.toDisplayString() + ':' + QString::number( start ) + '-' + QString::number( end ); } TimecodeTrack::~ TimecodeTrack() { } QString TimecodeTrack::name() const { return m_name; } QUrl TimecodeTrack::playableUrl() const { return m_playableUrl; } QString TimecodeTrack::uidUrl() const { return m_displayUrl; } QString TimecodeTrack::prettyUrl() const { return m_displayUrl; } QString TimecodeTrack::notPlayableReason() const { if( !m_playableUrl.isLocalFile() ) return i18n( "Url is not a local file" ); return localFileNotPlayableReason( m_playableUrl.toLocalFile() ); } AlbumPtr TimecodeTrack::album() const { return AlbumPtr::staticCast( m_album ); } ArtistPtr TimecodeTrack::artist() const { return ArtistPtr::staticCast( m_artist ); } GenrePtr TimecodeTrack::genre() const { return GenrePtr::staticCast( m_genre ); } ComposerPtr TimecodeTrack::composer() const { return ComposerPtr::staticCast( m_composer ); } YearPtr TimecodeTrack::year() const { return YearPtr::staticCast( m_year ); } qreal TimecodeTrack::bpm() const { return m_bpm; } QString TimecodeTrack::comment() const { return m_comment; } int TimecodeTrack::bitrate() const { return -1; } int TimecodeTrack::sampleRate() const { return -1; } int TimecodeTrack::filesize() const { return -1; } qint64 TimecodeTrack::length() const { return m_length; } int TimecodeTrack::trackNumber() const { return m_trackNumber; } int TimecodeTrack::discNumber() const { return m_discNumber; } QString TimecodeTrack::type() const { return QString(); } void TimecodeTrack::setAlbum( const QString &newAlbum ) { m_updatedFields |= ALBUM_UPDATED; m_fields.insert( ALBUM_UPDATED, newAlbum ); } void TimecodeTrack::setArtist( const QString &newArtist ) { m_updatedFields |= ARTIST_UPDATED; m_fields.insert( ARTIST_UPDATED, newArtist ); } void TimecodeTrack::setComposer( const QString &newComposer ) { m_updatedFields |= COMPOSER_UPDATED; m_fields.insert( COMPOSER_UPDATED, newComposer ); } void TimecodeTrack::setGenre( const QString &newGenre ) { m_updatedFields |= GENRE_UPDATED; m_fields.insert( GENRE_UPDATED, newGenre ); } void TimecodeTrack::setYear( int newYear ) { m_updatedFields |= YEAR_UPDATED; m_fields.insert( YEAR_UPDATED, QString::number( newYear ) ); } void TimecodeTrack::setBpm( const qreal newBpm ) { m_updatedFields |= BPM_UPDATED; m_fields.insert( BPM_UPDATED, QString::number( (qreal) newBpm ) ); } void TimecodeTrack::setTitle( const QString &newTitle ) { m_updatedFields |= TITLE_UPDATED; m_fields.insert( TITLE_UPDATED, newTitle ); } void TimecodeTrack::setComment( const QString &newComment ) { m_updatedFields |= COMMENT_UPDATED; m_fields.insert( COMMENT_UPDATED, newComment ); } void TimecodeTrack::setTrackNumber( int newTrackNumber ) { m_updatedFields |= TRACKNUMBER_UPDATED; m_fields.insert( TRACKNUMBER_UPDATED, QString::number( newTrackNumber ) ); } void TimecodeTrack::setDiscNumber( int newDiscNumber ) { m_updatedFields |= DISCNUMBER_UPDATED; m_fields.insert( DISCNUMBER_UPDATED, QString::number( newDiscNumber ) ); } void TimecodeTrack::beginUpdate() { m_updatedFields = 0; m_fields.clear(); } void TimecodeTrack::endUpdate() { bool updateCover = false; if ( m_updatedFields & ALBUM_UPDATED ) { //create a new album: m_album = TimecodeAlbumPtr( new TimecodeAlbum( m_fields.value( ALBUM_UPDATED ) ) ); m_album->addTrack( TimecodeTrackPtr( this ) ); setAlbum( m_album ); m_album->setAlbumArtist( m_artist ); } if ( m_updatedFields & ARTIST_UPDATED ) { //create a new album: m_artist = TimecodeArtistPtr( new TimecodeArtist( m_fields.value( ARTIST_UPDATED ) ) ); m_artist->addTrack( TimecodeTrackPtr( this ) ); setArtist( m_artist ); m_album->setAlbumArtist( m_artist ); updateCover = true; } if ( m_updatedFields & COMPOSER_UPDATED ) { //create a new album: m_composer = TimecodeComposerPtr( new TimecodeComposer( m_fields.value( COMPOSER_UPDATED ) ) ); m_composer->addTrack( TimecodeTrackPtr( this ) ); setComposer( m_composer ); } if ( m_updatedFields & GENRE_UPDATED ) { //create a new album: m_genre = TimecodeGenrePtr( new TimecodeGenre( m_fields.value( GENRE_UPDATED ) ) ); m_genre->addTrack( TimecodeTrackPtr( this ) ); setGenre( m_genre ); } if ( m_updatedFields & YEAR_UPDATED ) { //create a new album: m_year = TimecodeYearPtr( new TimecodeYear( m_fields.value( YEAR_UPDATED ) ) ); m_year->addTrack( TimecodeTrackPtr( this ) ); setYear( m_year ); } if ( m_updatedFields & BPM_UPDATED ) { m_bpm = m_fields.value( BPM_UPDATED ).toDouble(); } if ( m_updatedFields & TITLE_UPDATED ) { //create a new album: m_name = m_fields.value( TITLE_UPDATED ); updateCover = true; } if ( m_updatedFields & COMMENT_UPDATED ) { //create a new album: m_comment = m_fields.value( COMMENT_UPDATED ); } if ( m_updatedFields & TRACKNUMBER_UPDATED ) { //create a new album: m_trackNumber = m_fields.value( TRACKNUMBER_UPDATED ).toInt(); } if ( m_updatedFields & DISCNUMBER_UPDATED ) { //create a new album: m_discNumber = m_fields.value( DISCNUMBER_UPDATED ).toInt(); } if ( updateCover ) The::coverFetcher()->queueAlbum( AlbumPtr::staticCast( m_album ) ); m_updatedFields = 0; m_fields.clear(); notifyObservers(); } void TimecodeTrack::setAlbum( TimecodeAlbumPtr album ) { m_album = album; } void TimecodeTrack::setAlbumArtist( const QString & ) { // no suport for it } void TimecodeTrack::setYear( TimecodeYearPtr year ) { m_year = year; } void TimecodeTrack::setGenre( TimecodeGenrePtr genre ) { m_genre = genre; } void TimecodeTrack::setComposer( TimecodeComposerPtr composer ) { m_composer = composer; } void TimecodeTrack::setArtist( TimecodeArtistPtr artist ) { m_artist = artist; } bool TimecodeTrack::hasCapabilityInterface( Capabilities::Capability::Type type ) const { return type == Capabilities::Capability::BoundedPlayback; } Capabilities::Capability * TimecodeTrack::createCapabilityInterface( Capabilities::Capability::Type type ) { DEBUG_BLOCK if ( type == Capabilities::Capability::BoundedPlayback ) return new Capabilities::TimecodeBoundedPlaybackCapability( this ); else return 0; } TrackEditorPtr TimecodeTrack::editor() { return TrackEditorPtr( this ); } qint64 Meta::TimecodeTrack::start() { return m_start; } qint64 Meta::TimecodeTrack::end() { return m_end; } ////////////////// ARTIST ////////////////// TimecodeArtist::TimecodeArtist( const QString & name ) : m_name( name ) { } TimecodeArtist::~ TimecodeArtist() { } QString TimecodeArtist::name() const { return m_name; } TrackList TimecodeArtist::tracks() { return m_tracks; } AlbumList TimecodeArtist::albums() { return AlbumList(); } void TimecodeArtist::addTrack( TimecodeTrackPtr track ) { m_tracks.append( TrackPtr::staticCast( track ) ); } ////////////////// ALBUM ////////////////// TimecodeAlbum::TimecodeAlbum( const QString & name ) : QObject() , m_name( name ) , m_isCompilation( false ) { } TimecodeAlbum::~ TimecodeAlbum() { CoverCache::invalidateAlbum( this ); } QString TimecodeAlbum::name() const { return m_name; } bool TimecodeAlbum::isCompilation() const { return m_isCompilation; } bool TimecodeAlbum::hasAlbumArtist() const { return !m_albumArtist.isNull(); } ArtistPtr TimecodeAlbum::albumArtist() const { return ArtistPtr::staticCast( m_albumArtist ); } TrackList TimecodeAlbum::tracks() { return m_tracks; } QImage TimecodeAlbum::image( int size ) const { if( m_cover.isNull() ) return Meta::Album::image( size ); else return m_cover.scaled( size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation ); } bool TimecodeAlbum::canUpdateImage() const { return true; } void TimecodeAlbum::setImage( const QImage &image ) { m_cover = image; CoverCache::invalidateAlbum( this ); notifyObservers(); } void TimecodeAlbum::addTrack( TimecodeTrackPtr track ) { m_tracks.append( TrackPtr::staticCast( track ) ); } void TimecodeAlbum::setAlbumArtist( TimecodeArtistPtr artist ) { m_albumArtist = artist; } bool TimecodeAlbum::hasCapabilityInterface( Capabilities::Capability::Type type ) const { switch( type ) { case Capabilities::Capability::Actions: return true; default: return false; } } Capabilities::Capability* TimecodeAlbum::createCapabilityInterface( Capabilities::Capability::Type type ) { switch( type ) { case Capabilities::Capability::Actions: return new AlbumActionsCapability( Meta::AlbumPtr( this ) ); default: return 0; } } ////////////////// GENRE ////////////////// TimecodeGenre::TimecodeGenre(const QString & name) : m_name( name ) { } TimecodeGenre::~ TimecodeGenre() { } QString TimecodeGenre::name() const { return m_name; } TrackList TimecodeGenre::tracks() { - return tracks(); + return m_tracks; } void TimecodeGenre::addTrack( TimecodeTrackPtr track ) { m_tracks.append( TrackPtr::staticCast( track ) ); } ////////////////// COMPOSER ////////////////// TimecodeComposer::TimecodeComposer( const QString & name ) : m_name( name ) { } TimecodeComposer::~ TimecodeComposer() { } QString TimecodeComposer::name() const { return m_name; } TrackList TimecodeComposer::tracks() { return m_tracks; } void TimecodeComposer::addTrack( TimecodeTrackPtr track ) { m_tracks.append( TrackPtr::staticCast( track ) ); } ////////////////// YEAR ////////////////// TimecodeYear::TimecodeYear( const QString & name ) : m_name( name ) { } TimecodeYear::~ TimecodeYear() { } QString TimecodeYear::name() const { return m_name; } TrackList TimecodeYear::tracks() { return m_tracks; } void TimecodeYear::addTrack( TimecodeTrackPtr track ) { m_tracks.append( TrackPtr::staticCast( track ) ); } diff --git a/src/core-impl/playlists/types/file/PlaylistFileLoaderJob.h b/src/core-impl/playlists/types/file/PlaylistFileLoaderJob.h index e0d89868ff..e5ea033323 100644 --- a/src/core-impl/playlists/types/file/PlaylistFileLoaderJob.h +++ b/src/core-impl/playlists/types/file/PlaylistFileLoaderJob.h @@ -1,70 +1,70 @@ /**************************************************************************************** * Copyright (c) 2013 Tatjana Gornak * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #ifndef AMAROK_PLAYLISTFILELOADERJOB_H #define AMAROK_PLAYLISTFILELOADERJOB_H #include "core-impl/playlists/types/file/PlaylistFile.h" #include #include class KJob; namespace Playlists { /** * Allows threading during playlist file loading. Auto-deletes when its work is done. */ class PlaylistFileLoaderJob :public QObject, public ThreadWeaver::Job { Q_OBJECT public: explicit PlaylistFileLoaderJob( const PlaylistFilePtr &playlist ); protected: - void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) Q_DECL_OVERRIDE; - void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; + void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) override; + void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; 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); private Q_SLOTS: void slotDonwloadFinished( KJob *job ); /** * Responsible for notification of finished loading */ void slotDone(); private: PlaylistFilePtr m_playlist; QTemporaryFile m_tempFile; QString m_actualPlaylistFile; // path to local playlist file to actually load QSemaphore m_downloadSemaphore; }; } #endif diff --git a/src/core/support/Debug_p.h b/src/core/support/Debug_p.h index fc4fc24a0d..13f7ed3743 100644 --- a/src/core/support/Debug_p.h +++ b/src/core/support/Debug_p.h @@ -1,63 +1,63 @@ /* Copyright (c) 2010 Kevin Funk This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef DEBUGPRIVATE_H #define DEBUGPRIVATE_H #include "Debug.h" #include #include class IndentPrivate : public QObject { private: explicit IndentPrivate(QObject* parent = 0); public: static IndentPrivate* instance(); QString m_string; }; class NoDebugStream : public QIODevice { public: NoDebugStream() { open(WriteOnly); } - bool isSequential() const Q_DECL_OVERRIDE + bool isSequential() const override { return true; } - qint64 readData(char *, qint64) Q_DECL_OVERRIDE + qint64 readData(char *, qint64) override { return 0; } - qint64 readLineData(char *, qint64) Q_DECL_OVERRIDE + qint64 readLineData(char *, qint64) override { return 0; } - qint64 writeData(const char *, qint64 len) Q_DECL_OVERRIDE + qint64 writeData(const char *, qint64 len) override { return len; } }; #endif // DEBUGPRIVATE_H diff --git a/src/covermanager/CoverFetchQueue.cpp b/src/covermanager/CoverFetchQueue.cpp index bf3914308b..177455efe8 100644 --- a/src/covermanager/CoverFetchQueue.cpp +++ b/src/covermanager/CoverFetchQueue.cpp @@ -1,173 +1,162 @@ /**************************************************************************************** * Copyright (c) 2009 Rick W. Chen * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #include "CoverFetchQueue.h" + CoverFetchQueue::CoverFetchQueue( QObject *parent ) : QObject( parent ) { } CoverFetchQueue::~CoverFetchQueue() { } void CoverFetchQueue::add( const CoverFetchUnit::Ptr unit ) { m_queue.append( unit ); emit fetchUnitAdded( unit ); } void CoverFetchQueue::add( const Meta::AlbumPtr album, const CoverFetch::Option opt, const CoverFetch::Source src, const QByteArray &xml ) { CoverFetchPayload *payload; if( xml.isEmpty() ) { payload = new CoverFetchInfoPayload( album, src ); } else { CoverFetch::ImageSize imageSize; if( opt == CoverFetch::Automatic ) imageSize = CoverFetch::NormalSize; else imageSize = CoverFetch::ThumbSize; const bool wild = ( opt == CoverFetch::WildInteractive ) ? true : false; CoverFetchArtPayload *art = new CoverFetchArtPayload( album, imageSize, src, wild ); art->setXml( xml ); payload = art; } add( AmarokSharedPointer< CoverFetchUnit >( new CoverFetchUnit( album, payload, opt ) ) ); } void CoverFetchQueue::add( const CoverFetch::Option opt, const CoverFetch::Source src, const QByteArray &xml ) { switch( src ) { default: case CoverFetch::Google: case CoverFetch::LastFm: { typedef CoverFetchArtPayload CFAP; const bool wild = ( opt == CoverFetch::WildInteractive ) ? true : false; CFAP *payload = new CFAP( CoverFetch::ThumbSize, src, wild ); payload->setXml( xml ); add( AmarokSharedPointer< CoverFetchUnit >( new CoverFetchUnit( payload, opt ) ) ); } break; case CoverFetch::Discogs: { typedef CoverFetchInfoPayload CFIP; CFIP *payload = new CFIP( src, xml ); add( AmarokSharedPointer< CoverFetchUnit >( new CoverFetchUnit( payload, opt ) ) ); } break; } } void CoverFetchQueue::addQuery( const QString &query, const CoverFetch::Source src, unsigned int page, Meta::AlbumPtr album ) { CoverFetchSearchPayload *payload = new CoverFetchSearchPayload( query, src, page, album ); add( AmarokSharedPointer< CoverFetchUnit >( new CoverFetchUnit( payload ) ) ); } -int -CoverFetchQueue::size() const -{ - return m_queue.size(); -} - -bool -CoverFetchQueue::isEmpty() const -{ - return m_queue.isEmpty(); -} - void CoverFetchQueue::clear() { m_queue.clear(); } void CoverFetchQueue::remove( const CoverFetchUnit::Ptr unit ) { m_queue.removeAll( unit ); } void CoverFetchQueue::remove( const Meta::AlbumPtr album ) { m_queue.removeAt( index( album ) ); } bool CoverFetchQueue::contains( const Meta::AlbumPtr album ) const { typedef CoverFetchUnitList::const_iterator ListIter; ListIter it = m_queue.constBegin(); ListIter last = m_queue.constEnd(); while( it != last ) { Meta::AlbumPtr t_album = (*it)->album(); if( t_album == album ) return true; ++it; } return false; } int CoverFetchQueue::index( const Meta::AlbumPtr album ) const { for( int i = 0, len = m_queue.size(); i < len; ++i ) { if( m_queue.at( i )->album() == album ) return i; } return -1; } const CoverFetchUnit::Ptr CoverFetchQueue::take( const Meta::AlbumPtr album ) { - for( int i = 0, end = this->size(); i < end; ++i ) + for( int i = 0, end = m_queue.size(); i < end; ++i ) { const CoverFetchUnit::Ptr unit = m_queue.at( i ); if( unit->album() == album ) { m_queue.removeAt( i ); return unit; } } // need to test if album exists with contains() first return AmarokSharedPointer< CoverFetchUnit >(); } diff --git a/src/covermanager/CoverFetchQueue.h b/src/covermanager/CoverFetchQueue.h index 5a069f0d88..2f66aab3c0 100644 --- a/src/covermanager/CoverFetchQueue.h +++ b/src/covermanager/CoverFetchQueue.h @@ -1,108 +1,105 @@ /**************************************************************************************** * Copyright (c) 2009 Rick W. Chen * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #ifndef AMAROK_COVERFETCHQUEUE_H #define AMAROK_COVERFETCHQUEUE_H #include "core/meta/forward_declarations.h" #include "CoverFetchUnit.h" #include #include #include #include #include class CoverFetchPayload; typedef QList< CoverFetchUnit::Ptr > CoverFetchUnitList; /** * A queue that creates and keeps track of cover fetching units. * This queue creates cover fetch units with suitable payloads as requested by * the cover fetcher. It does not manage the state of those units, as in, what * to do next after each of those units is completed. The cover fetcher is * responsible for coordinating that. */ class CoverFetchQueue : public QObject { Q_OBJECT public: explicit CoverFetchQueue( QObject *parent = 0 ); ~CoverFetchQueue(); /** * Add an album-associated work unit to the queue. * @param album album to fetch cover for. * @param opt cover fetch option. * @param src cover image source. * @param xml xml document from the cover provider. Can be empty on first * pass of the fetching process. */ void add( const Meta::AlbumPtr album, const CoverFetch::Option opt = CoverFetch::Automatic, const CoverFetch::Source src = CoverFetch::LastFm, const QByteArray &xml = QByteArray() ); /** * Add a work unit to the queue that does not need to associate with any album. * @param opt cover fetch option. * @param src cover image source. * @param xml xml document from the cover provider. Can be empty on first * pass of the fetching process. */ void add( const CoverFetch::Option opt = CoverFetch::WildInteractive, const CoverFetch::Source src = CoverFetch::LastFm, const QByteArray &xml = QByteArray() ); /** * Add a string query to the queue. * @param query text to be used for image search. * @param src the image provider to search. * @param page the page number to jump to. * @param album optional album this query is associated with */ void addQuery( const QString &query, const CoverFetch::Source src = CoverFetch::LastFm, unsigned int page = 0, Meta::AlbumPtr album = Meta::AlbumPtr(0) ); - bool contains( const Meta::AlbumPtr album ) const; - int index( const Meta::AlbumPtr album ) const; - int size() const; - bool isEmpty() const; - void clear(); - const CoverFetchUnit::Ptr take( const Meta::AlbumPtr album ); public Q_SLOTS: void remove( const CoverFetchUnit::Ptr unit ); void remove( const Meta::AlbumPtr album ); Q_SIGNALS: void fetchUnitAdded( CoverFetchUnit::Ptr ); private: void add( const CoverFetchUnit::Ptr unit ); + bool contains( const Meta::AlbumPtr album ) const; + int index( const Meta::AlbumPtr album ) const; + const CoverFetchUnit::Ptr take( const Meta::AlbumPtr album ); CoverFetchUnitList m_queue; Q_DISABLE_COPY( CoverFetchQueue ) }; #endif /* AMAROK_COVERFETCHQUEUE_H */ diff --git a/src/covermanager/CoverFetcher.cpp b/src/covermanager/CoverFetcher.cpp index 83550d38e7..faec6cfb10 100644 --- a/src/covermanager/CoverFetcher.cpp +++ b/src/covermanager/CoverFetcher.cpp @@ -1,485 +1,476 @@ /**************************************************************************************** * Copyright (c) 2004 Mark Kretschmann * * Copyright (c) 2004 Stefan Bogner * * Copyright (c) 2004 Max Howell * * Copyright (c) 2007 Dan Meltzer * * Copyright (c) 2009 Martin Sandsmark * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General 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 "CoverFetcher" #include "CoverFetcher.h" #include "amarokconfig.h" #include "core/logger/Logger.h" #include "core/meta/Meta.h" #include "core/support/Amarok.h" #include "core/support/Components.h" #include "core/support/Debug.h" #include "CoverFetchQueue.h" #include "CoverFoundDialog.h" #include "CoverFetchUnit.h" -#include -#include - #include #include +#include #include #include #include -CoverFetcher* CoverFetcher::s_instance = 0; +#include +#include + + +CoverFetcher* CoverFetcher::s_instance = nullptr; CoverFetcher* CoverFetcher::instance() { return s_instance ? s_instance : new CoverFetcher(); } void CoverFetcher::destroy() { if( s_instance ) { delete s_instance; - s_instance = 0; + s_instance = nullptr; } } CoverFetcher::CoverFetcher() : QObject() - , m_limit( 10 ) { DEBUG_BLOCK setObjectName( "CoverFetcher" ); qRegisterMetaType("CoverFetchUnit::Ptr"); - m_queue = new CoverFetchQueue( this ); + s_instance = this; + + m_queueThread = new QThread( this ); + m_queueThread->start(); + m_queue = new CoverFetchQueue; + m_queue->moveToThread( m_queueThread ); + connect( m_queue, &CoverFetchQueue::fetchUnitAdded, this, &CoverFetcher::slotFetch ); - s_instance = this; connect( The::networkAccessManager(), &NetworkAccessManagerProxy::requestRedirectedReply, this, &CoverFetcher::fetchRequestRedirected ); } CoverFetcher::~CoverFetcher() { + m_queue->deleteLater(); + m_queueThread->quit(); + m_queueThread->wait(); } void CoverFetcher::manualFetch( Meta::AlbumPtr album ) { debug() << QString("Adding interactive cover fetch for: '%1' from %2") .arg( album->name(), Amarok::config("Cover Fetcher").readEntry("Interactive Image Source", "LastFm") ); switch( fetchSource() ) { case CoverFetch::LastFm: - m_queue->add( album, CoverFetch::Interactive, fetchSource() ); + QTimer::singleShot( 0, m_queue, [=] () { m_queue->add( album, CoverFetch::Interactive, fetchSource() ); } ); break; case CoverFetch::Discogs: case CoverFetch::Google: queueQueryForAlbum( album ); break; default: break; } } void CoverFetcher::queueAlbum( Meta::AlbumPtr album ) { - if( m_queue->size() > m_limit ) - m_queueLater.append( album ); - else - m_queue->add( album, CoverFetch::Automatic ); + QTimer::singleShot( 0, m_queue, [=] () { m_queue->add( album, CoverFetch::Automatic ); } ); debug() << "Queueing automatic cover fetch for:" << album->name(); } void CoverFetcher::queueAlbums( Meta::AlbumList albums ) { foreach( Meta::AlbumPtr album, albums ) { - if( m_queue->size() > m_limit ) - m_queueLater.append( album ); - else - m_queue->add( album, CoverFetch::Automatic ); + QTimer::singleShot( 0, m_queue, [=] () { m_queue->add( album, CoverFetch::Automatic ); } ); } } void CoverFetcher::queueQuery( Meta::AlbumPtr album, const QString &query, int page ) { - m_queue->addQuery( query, fetchSource(), page, album ); + QTimer::singleShot( 0, m_queue, [=] () { m_queue->addQuery( query, fetchSource(), page, album ); } ); debug() << QString( "Queueing cover fetch query: '%1' (page %2)" ).arg( query, QString::number( page ) ); } void CoverFetcher::queueQueryForAlbum( Meta::AlbumPtr album ) { QString query( album->name() ); if( album->hasAlbumArtist() ) query += ' ' + album->albumArtist()->name(); queueQuery( album, query, 1 ); } void CoverFetcher::slotFetch( CoverFetchUnit::Ptr unit ) { if( !unit ) return; const CoverFetchPayload *payload = unit->payload(); const CoverFetch::Urls urls = payload->urls(); // show the dialog straight away if fetch is interactive if( !m_dialog && unit->isInteractive() ) { showCover( unit, QImage() ); } else if( urls.isEmpty() ) { finish( unit, NotFound ); return; } const QList uniqueUrls = urls.uniqueKeys(); foreach( const QUrl &url, uniqueUrls ) { if( !url.isValid() ) continue; QNetworkReply *reply = The::networkAccessManager()->getData( url, this, &CoverFetcher::slotResult ); m_urls.insert( url, unit ); if( payload->type() == CoverFetchPayload::Art ) { if( unit->isInteractive() ) Amarok::Logger::newProgressOperation( reply, i18n( "Fetching Cover" ) ); else return; // only one is needed when the fetch is non-interactive } } } void -CoverFetcher::slotResult( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ) +CoverFetcher::slotResult( const QUrl &url, const QByteArray &data, NetworkAccessManagerProxy::Error e ) { DEBUG_BLOCK if( !m_urls.contains( url ) ) return; - debug() << "Data dump from the result: " << data; +// debug() << "Data dump from the result: " << data; const CoverFetchUnit::Ptr unit( m_urls.take( url ) ); if( !unit ) { - m_queue->remove( unit ); + QTimer::singleShot( 0, m_queue, [=] () { m_queue->remove( unit ); } ); return; } if( e.code != QNetworkReply::NoError ) { finish( unit, Error, i18n("There was an error communicating with cover provider: %1", e.description) ); return; } const CoverFetchPayload *payload = unit->payload(); switch( payload->type() ) { case CoverFetchPayload::Info: - m_queue->add( unit->album(), unit->options(), payload->source(), data ); - m_queue->remove( unit ); + QTimer::singleShot( 0, m_queue, [=] () { m_queue->add( unit->album(), unit->options(), payload->source(), data ); + m_queue->remove( unit ); } ); break; case CoverFetchPayload::Search: - m_queue->add( unit->options(), fetchSource(), data ); - m_queue->remove( unit ); + QTimer::singleShot( 0, m_queue, [=] () { m_queue->add( unit->options(), fetchSource(), data ); + m_queue->remove( unit ); } ); break; case CoverFetchPayload::Art: handleCoverPayload( unit, data, url ); break; } } void CoverFetcher::handleCoverPayload( const CoverFetchUnit::Ptr &unit, const QByteArray &data, const QUrl &url ) { if( data.isEmpty() ) { finish( unit, NotFound ); return; } QBuffer buffer; buffer.setData( data ); buffer.open( QIODevice::ReadOnly ); QImageReader reader( &buffer ); if( !reader.canRead() ) { finish( unit, Error, reader.errorString() ); return; } QSize imageSize = reader.size(); const CoverFetchArtPayload *payload = static_cast( unit->payload() ); const CoverFetch::Metadata &metadata = payload->urls().value( url ); if( payload->imageSize() == CoverFetch::ThumbSize ) { if( imageSize.isEmpty() ) { imageSize.setWidth( metadata.value( QLatin1String("width") ).toInt() ); imageSize.setHeight( metadata.value( QLatin1String("height") ).toInt() ); } imageSize.scale( 120, 120, Qt::KeepAspectRatio ); reader.setScaledSize( imageSize ); // This will force the JPEG decoder to use JDCT_IFAST reader.setQuality( 49 ); } if( unit->isInteractive() ) { QImage image; if( reader.read( &image ) ) { showCover( unit, image, metadata ); - m_queue->remove( unit ); + QTimer::singleShot( 0, m_queue, [=] () { m_queue->remove( unit ); } ); return; } } else { QImage image; if( reader.read( &image ) ) { m_selectedImages.insert( unit, image ); finish( unit ); return; } } finish( unit, Error, reader.errorString() ); } void CoverFetcher::slotDialogFinished() { const CoverFetchUnit::Ptr unit = m_dialog->unit(); switch( m_dialog->result() ) { case QDialog::Accepted: m_selectedImages.insert( unit, m_dialog->image() ); finish( unit ); break; case QDialog::Rejected: finish( unit, Cancelled ); break; default: finish( unit, Error ); } /* * Remove all manual fetch jobs from the queue if the user accepts, cancels, * or closes the cover found dialog. This way, the dialog will not reappear * if there are still covers yet to be retrieved. */ QList< CoverFetchUnit::Ptr > units = m_urls.values(); foreach( const CoverFetchUnit::Ptr &unit, units ) { if( unit->isInteractive() ) abortFetch( unit ); } m_dialog->hide(); m_dialog->deleteLater(); } void CoverFetcher::fetchRequestRedirected( QNetworkReply *oldReply, QNetworkReply *newReply ) { QUrl oldUrl = oldReply->request().url(); QUrl newUrl = newReply->request().url(); // Since we were redirected we have to check if the redirect // was for one of our URLs and if the new URL is not handled // already. if( m_urls.contains( oldUrl ) && !m_urls.contains( newUrl ) ) { // Get the unit for the old URL. CoverFetchUnit::Ptr unit = m_urls.value( oldUrl ); // Add the unit with the new URL and remove the old one. m_urls.insert( newUrl, unit ); m_urls.remove( oldUrl ); // If the unit is an interactive one we have to incidate that we're // still fetching the cover. if( unit->isInteractive() ) Amarok::Logger::newProgressOperation( newReply, i18n( "Fetching Cover" ) ); } } void CoverFetcher::showCover( const CoverFetchUnit::Ptr &unit, const QImage &cover, const CoverFetch::Metadata &data ) { if( !m_dialog ) { const Meta::AlbumPtr album = unit->album(); if( !album ) { finish( unit, Error ); return; } - m_dialog = new CoverFoundDialog( unit, data, static_cast( parent() ) ); + m_dialog = new CoverFoundDialog( unit, data ); connect( m_dialog.data(), &CoverFoundDialog::newCustomQuery, this, &CoverFetcher::queueQuery ); connect( m_dialog.data(), &CoverFoundDialog::accepted, this, &CoverFetcher::slotDialogFinished ); connect( m_dialog.data(),&CoverFoundDialog::rejected, this, &CoverFetcher::slotDialogFinished ); if( fetchSource() == CoverFetch::LastFm ) queueQueryForAlbum( album ); m_dialog->setQueryPage( 1 ); m_dialog->show(); m_dialog->raise(); m_dialog->activateWindow(); } else { if( !cover.isNull() ) { typedef CoverFetchArtPayload CFAP; const CFAP *payload = dynamic_cast< const CFAP* >( unit->payload() ); if( payload ) m_dialog->add( cover, data, payload->imageSize() ); } } } void CoverFetcher::abortFetch( CoverFetchUnit::Ptr unit ) { - m_queue->remove( unit ); - m_queueLater.removeAll( unit->album() ); + QTimer::singleShot( 0, m_queue, [=] () { m_queue->remove( unit ); } ); m_selectedImages.remove( unit ); QList urls = m_urls.keys( unit ); foreach( const QUrl &url, urls ) m_urls.remove( url ); The::networkAccessManager()->abortGet( urls ); } void CoverFetcher::finish( const CoverFetchUnit::Ptr unit, CoverFetcher::FinishState state, const QString &message ) { Meta::AlbumPtr album = unit->album(); const QString albumName = album ? album->name() : QString(); switch( state ) { case Success: + { if( !albumName.isEmpty() ) { const QString text = i18n( "Retrieved cover successfully for '%1'.", albumName ); Amarok::Logger::shortMessage( text ); debug() << "Finished successfully for album" << albumName; } - album->setImage( m_selectedImages.take( unit ) ); + QImage image = m_selectedImages.take( unit ); + std::thread thread( std::bind( &Meta::Album::setImage, album, image ) ); + thread.detach(); abortFetch( unit ); break; - + } case Error: if( !albumName.isEmpty() ) { const QString text = i18n( "Fetching cover for '%1' failed.", albumName ); Amarok::Logger::shortMessage( text ); QString debugMessage; if( !message.isEmpty() ) debugMessage = '[' + message + ']'; debug() << "Finished with errors for album" << albumName << debugMessage; } m_errors += message; break; case Cancelled: if( !albumName.isEmpty() ) { const QString text = i18n( "Canceled fetching cover for '%1'.", albumName ); Amarok::Logger::shortMessage( text ); debug() << "Finished, cancelled by user for album" << albumName; } break; case NotFound: if( !albumName.isEmpty() ) { const QString text = i18n( "Unable to find a cover for '%1'.", albumName ); //FIXME: Not visible behind cover manager Amarok::Logger::shortMessage( text ); m_errors += text; debug() << "Finished due to cover not found for album" << albumName; } break; } - m_queue->remove( unit ); - - if( !m_queueLater.isEmpty() ) - { - const int diff = m_limit - m_queue->size(); - if( diff > 0 ) - { - for( int i = 0; i < diff && !m_queueLater.isEmpty(); ++i ) - { - Meta::AlbumPtr album = m_queueLater.takeFirst(); - // automatic fetching only uses Last.fm as source - m_queue->add( album, CoverFetch::Automatic, CoverFetch::LastFm ); - } - } - } + QTimer::singleShot( 0, m_queue, [=] () { m_queue->remove( unit ); } ); emit finishedSingle( static_cast< int >( state ) ); } CoverFetch::Source CoverFetcher::fetchSource() const { const KConfigGroup config = Amarok::config( "Cover Fetcher" ); const QString sourceEntry = config.readEntry( "Interactive Image Source", "LastFm" ); CoverFetch::Source source; if( sourceEntry == "LastFm" ) source = CoverFetch::LastFm; else if( sourceEntry == "Google" ) source = CoverFetch::Google; else source = CoverFetch::Discogs; return source; } diff --git a/src/covermanager/CoverFetcher.h b/src/covermanager/CoverFetcher.h index d18f573b78..0feb59a18c 100644 --- a/src/covermanager/CoverFetcher.h +++ b/src/covermanager/CoverFetcher.h @@ -1,116 +1,116 @@ /**************************************************************************************** * Copyright (c) 2004 Mark Kretschmann * * Copyright (c) 2004 Stefan Bogner * * Copyright (c) 2007 Dan Meltzer * * Copyright (c) 2009 Martin Sandsmark * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General 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_COVERFETCHER_H #define AMAROK_COVERFETCHER_H #include "core/meta/forward_declarations.h" #include "CoverFetchUnit.h" #include "network/NetworkAccessManagerProxy.h" #include #include //baseclass #include #include //stack allocated class CoverFetchQueue; class CoverFoundDialog; +class QThread; namespace KIO { class Job; } class CoverFetcher : public QObject { Q_OBJECT public: AMAROK_EXPORT static CoverFetcher* instance(); AMAROK_EXPORT static void destroy(); AMAROK_EXPORT void manualFetch( Meta::AlbumPtr album ); AMAROK_EXPORT void queueAlbum( Meta::AlbumPtr album ); AMAROK_EXPORT void queueAlbums( Meta::AlbumList albums ); QStringList errors() const { return m_errors; } enum FinishState { Success, Error, NotFound, Cancelled }; public Q_SLOTS: AMAROK_EXPORT void queueQuery( Meta::AlbumPtr album, const QString &query, int page = 0 ); Q_SIGNALS: void finishedSingle( int state ); private Q_SLOTS: /// Fetch a cover void slotFetch( CoverFetchUnit::Ptr unit ); /// Handle result of a fetch job - void slotResult( const QUrl &url, QByteArray data, NetworkAccessManagerProxy::Error e ); + void slotResult( const QUrl &url, const QByteArray &data, NetworkAccessManagerProxy::Error e ); /// Cover found dialog is closed by the user void slotDialogFinished(); /// The fetch request was redirected. void fetchRequestRedirected( QNetworkReply *oldReply, QNetworkReply *newReply ); private: static CoverFetcher* s_instance; CoverFetcher(); ~CoverFetcher(); /// Remove a fetch unit from the queue, and clean up any running jobs void abortFetch( CoverFetchUnit::Ptr unit ); void queueQueryForAlbum( Meta::AlbumPtr album ); - const int m_limit; //!< maximum number of concurrent fetches CoverFetchQueue *m_queue; //!< current fetch queue - Meta::AlbumList m_queueLater; //!< put here if m_queue exceeds m_limit + QThread *m_queueThread; QHash< QUrl, CoverFetchUnit::Ptr > m_urls; QHash< const CoverFetchUnit::Ptr, QImage > m_selectedImages; QStringList m_errors; QPointer m_dialog; /// cleanup depending on the fetch result void finish( const CoverFetchUnit::Ptr unit, FinishState state = Success, const QString &message = QString() ); /// Show the cover that has been found void showCover( const CoverFetchUnit::Ptr &unit, const QImage &cover = QImage(), const CoverFetch::Metadata &data = CoverFetch::Metadata() ); void handleCoverPayload( const CoverFetchUnit::Ptr &unit, const QByteArray &data, const QUrl &url ); CoverFetch::Source fetchSource() const; }; namespace The { inline CoverFetcher *coverFetcher() { return CoverFetcher::instance(); } } #endif /* AMAROK_COVERFETCHER_H */ diff --git a/src/dialogs/TagDialog.cpp b/src/dialogs/TagDialog.cpp index c32aae3ff9..b1fd8b2d77 100644 --- a/src/dialogs/TagDialog.cpp +++ b/src/dialogs/TagDialog.cpp @@ -1,1414 +1,1405 @@ /**************************************************************************************** * Copyright (c) 2004 Mark Kretschmann * * Copyright (c) 2004 Pierpaolo Di Panfilo * * Copyright (c) 2005-2006 Alexandre Pereira de Oliveira * * Copyright (c) 2008 Téo Mrnjavac * * Copyright (c) 2008 Leo Franchi * * Copyright (c) 2009 Daniel Dewald * * Copyright (c) 2009 Pierre Dumuid * * Copyright (c) 2011 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 "TagDialog" #include "TagDialog.h" #include "MainWindow.h" #include "SvgHandler.h" #include "core/collections/QueryMaker.h" #include "core/logger/Logger.h" #include "core/meta/Statistics.h" #include "core/meta/TrackEditor.h" #include "core/meta/support/MetaUtility.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/CoverFetchingActions.h" #include "dialogs/MusicBrainzTagger.h" #include "widgets/CoverLabel.h" #include "widgets/FilenameLayoutWidget.h" #include "ui_TagDialogBase.h" // needs to be after including CoverLabel, silly #include "TagGuesserDialog.h" #include #include #include #include +#include + + namespace Meta { namespace Field { const QString LABELS = "labels"; const QString LYRICS = "lyrics"; const QString TYPE = "type"; const QString COLLECTION = "collection"; const QString NOTE = "note"; } } TagDialog::TagDialog( const Meta::TrackList &tracks, QWidget *parent ) : QDialog( parent ) , m_perTrack( true ) , m_currentTrackNum( 0 ) , m_changed( false ) , m_queryMaker( 0 ) , ui( new Ui::TagDialogBase() ) { DEBUG_BLOCK foreach( Meta::TrackPtr track, tracks ) addTrack( track ); ui->setupUi( this ); resize( minimumSizeHint() ); initUi(); setCurrentTrack( 0 ); } TagDialog::TagDialog( Meta::TrackPtr track, QWidget *parent ) : QDialog( parent ) , m_perTrack( true ) , m_currentTrackNum( 0 ) , m_changed( false ) , m_queryMaker( 0 ) , ui( new Ui::TagDialogBase() ) { DEBUG_BLOCK addTrack( track ); ui->setupUi( this ); resize( minimumSizeHint() ); initUi(); setCurrentTrack( 0 ); QTimer::singleShot( 0, this, &TagDialog::show ); } TagDialog::TagDialog( Collections::QueryMaker *qm ) : QDialog( The::mainWindow() ) , m_perTrack( true ) , m_currentTrackNum( 0 ) , m_changed( false ) , m_queryMaker( qm ) , ui( new Ui::TagDialogBase() ) { DEBUG_BLOCK ui->setupUi( this ); resize( minimumSizeHint() ); qm->setQueryType( Collections::QueryMaker::Track ); connect( qm, &Collections::QueryMaker::newArtistsReady, this, &TagDialog::artistsReady, Qt::QueuedConnection ); connect( qm, &Collections::QueryMaker::newTracksReady, this, &TagDialog::tracksReady, Qt::QueuedConnection ); connect( qm, &Collections::QueryMaker::newAlbumsReady, this, &TagDialog::albumsReady, Qt::QueuedConnection ); connect( qm, &Collections::QueryMaker::newComposersReady, this, &TagDialog::composersReady, Qt::QueuedConnection ); connect( qm, &Collections::QueryMaker::newGenresReady, this, &TagDialog::genresReady, Qt::QueuedConnection ); connect( qm, &Collections::QueryMaker::newLabelsReady, this, &TagDialog::labelsReady, Qt::QueuedConnection ); connect( qm, &Collections::QueryMaker::queryDone, this, &TagDialog::queryDone, Qt::QueuedConnection ); qm->run(); } TagDialog::~TagDialog() { DEBUG_BLOCK Amarok::config( "TagDialog" ).writeEntry( "CurrentTab", ui->qTabWidget->currentIndex() ); - if( m_currentTrack && m_currentTrack->album() ) - unsubscribeFrom( m_currentTrack->album() ); + if( m_currentAlbum ) + unsubscribeFrom( m_currentAlbum ); delete ui; } void TagDialog::metadataChanged( Meta::AlbumPtr album ) { - if( !m_currentTrack || !m_currentTrack->album() ) + if( m_currentAlbum ) return; // If the metadata of the current album has changed, reload the cover - if( album == m_currentTrack->album() ) + if( album == m_currentAlbum ) updateCover(); // TODO: if the lyrics changed: should we show a warning and ask the user // if he wants to use the new lyrics? } //////////////////////////////////////////////////////////////////////////////// // PRIVATE SLOTS //////////////////////////////////////////////////////////////////////////////// void TagDialog::addTrack( Meta::TrackPtr &track ) { if( !m_tracks.contains( track ) ) { m_tracks.append( track ); m_storedTags.insert( track, getTagsFromTrack( track ) ); } } void TagDialog::tracksReady( const Meta::TrackList &tracks ) { foreach( Meta::TrackPtr track, tracks ) addTrack( track ); } void TagDialog::queryDone() { delete m_queryMaker; if( !m_tracks.isEmpty() ) { initUi(); setCurrentTrack( 0 ); QTimer::singleShot( 0, this, &TagDialog::show ); } else { deleteLater(); } } void TagDialog::albumsReady( const Meta::AlbumList &albums ) { foreach( const Meta::AlbumPtr &album, albums ) { if( !album->name().isEmpty() ) m_albums << album->name(); if( album->hasAlbumArtist() && !album->albumArtist()->name().isEmpty() ) m_albumArtists << album->albumArtist()->name(); } } void TagDialog::artistsReady( const Meta::ArtistList &artists ) { foreach( const Meta::ArtistPtr &artist, artists ) { if( !artist->name().isEmpty() ) m_artists << artist->name(); } } void TagDialog::composersReady( const Meta::ComposerList &composers ) { foreach( const Meta::ComposerPtr &composer, composers ) { if( !composer->name().isEmpty() ) m_composers << composer->name(); } } void TagDialog::genresReady( const Meta::GenreList &genres ) { foreach( const Meta::GenrePtr &genre, genres ) { if( !genre->name().isEmpty() ) // Where the heck do the empty genres come from? m_genres << genre->name(); } } void TagDialog::labelsReady( const Meta::LabelList &labels ) { foreach( const Meta::LabelPtr &label, labels ) { if( !label->name().isEmpty() ) m_allLabels << label->name(); } } void TagDialog::dataQueryDone() { // basically we want to ignore the fact that the fields are being // edited because we do it not the user, so it results in empty // tags being saved to files---data loss is BAD! bool oldChanged = m_changed; //we simply clear the completion data of all comboboxes //then load the current track again. that's more work than necessary //but the performance impact should be negligible // we do this because if we insert items and the contents of the textbox // are not in the list, it clears the textbox. which is bad --lfranchi 2.22.09 QString saveText( ui->kComboBox_artist->lineEdit()->text() ); QStringList artists = m_artists.toList(); artists.sort(); ui->kComboBox_artist->clear(); ui->kComboBox_artist->insertItems( 0, artists ); ui->kComboBox_artist->completionObject()->setItems( artists ); ui->kComboBox_artist->lineEdit()->setText( saveText ); saveText = ui->kComboBox_album->lineEdit()->text(); QStringList albums = m_albums.toList(); albums.sort(); ui->kComboBox_album->clear(); ui->kComboBox_album->insertItems( 0, albums ); ui->kComboBox_album->completionObject()->setItems( albums ); ui->kComboBox_album->lineEdit()->setText( saveText ); saveText = ui->kComboBox_albumArtist->lineEdit()->text(); QStringList albumArtists = m_albumArtists.toList(); albumArtists.sort(); ui->kComboBox_albumArtist->clear(); ui->kComboBox_albumArtist->insertItems( 0, albumArtists ); ui->kComboBox_albumArtist->completionObject()->setItems( albumArtists ); ui->kComboBox_albumArtist->lineEdit()->setText( saveText ); saveText = ui->kComboBox_composer->lineEdit()->text(); QStringList composers = m_composers.toList(); composers.sort(); ui->kComboBox_composer->clear(); ui->kComboBox_composer->insertItems( 0, composers ); ui->kComboBox_composer->completionObject()->setItems( composers ); ui->kComboBox_composer->lineEdit()->setText( saveText ); saveText = ui->kComboBox_genre->lineEdit()->text(); QStringList genres = m_genres.toList(); genres.sort(); ui->kComboBox_genre->clear(); ui->kComboBox_genre->insertItems( 0, genres ); ui->kComboBox_genre->completionObject()->setItems( genres ); ui->kComboBox_genre->lineEdit()->setText( saveText ); saveText = ui->kComboBox_label->lineEdit()->text(); QStringList labels = m_allLabels.toList(); labels.sort(); ui->kComboBox_label->clear(); ui->kComboBox_label->insertItems( 0, labels ); ui->kComboBox_label->completionObject()->setItems( labels ); ui->kComboBox_label->lineEdit()->setText( saveText ); m_changed = oldChanged; } void TagDialog::removeLabelPressed() //SLOT { if( ui->labelsList->selectionModel()->hasSelection() ) { QModelIndexList idxList = ui->labelsList->selectionModel()->selectedRows(); QStringList selection; for( int x = 0; x < idxList.size(); ++x ) { QString label = idxList.at(x).data( Qt::DisplayRole ).toString(); selection.append( label ); } m_labelModel->removeLabels( selection ); ui->labelsList->selectionModel()->reset(); labelSelected(); checkChanged(); } } void TagDialog::addLabelPressed() //SLOT { QString label = ui->kComboBox_label->currentText(); if( !label.isEmpty() ) { m_labelModel->addLabel( label ); ui->kComboBox_label->setCurrentIndex( -1 ); ui->kComboBox_label->completionObject()->insertItems( QStringList( label ) ); if ( !ui->kComboBox_label->contains( label ) ) ui->kComboBox_label->addItem( label ); checkChanged(); } } void TagDialog::cancelPressed() //SLOT { QApplication::restoreOverrideCursor(); // restore the cursor before closing the dialog (The musicbrainz dialog might have set it) reject(); } void TagDialog::accept() //SLOT { ui->pushButton_ok->setEnabled( false ); //visual feedback saveTags(); QDialog::accept(); } inline void TagDialog::openPressed() //SLOT { new KRun( QUrl::fromLocalFile(m_path), this ); } inline void TagDialog::previousTrack() //SLOT { setCurrentTrack( m_currentTrackNum - 1 ); } inline void TagDialog::nextTrack() //SLOT { setCurrentTrack( m_currentTrackNum + 1 ); } inline void TagDialog::perTrack( bool enabled ) //SLOT { if( enabled == m_perTrack ) return; setTagsToTrack(); setPerTrack( enabled ); setTagsToUi(); } void TagDialog::checkChanged() //SLOT { QVariantMap oldTags; if( m_perTrack ) oldTags = m_storedTags.value( m_currentTrack ); else oldTags = getTagsFromMultipleTracks(); QVariantMap newTags = getTagsFromUi( oldTags ); ui->pushButton_ok->setEnabled( m_changed || !newTags.isEmpty() ); } inline void TagDialog::labelModified() //SLOT { ui->addButton->setEnabled( ui->kComboBox_label->currentText().length()>0 ); } inline void TagDialog::labelSelected() //SLOT { ui->removeButton->setEnabled( ui->labelsList->selectionModel()->hasSelection() ); } //creates a QDialog and executes the FilenameLayoutWidget. Grabs a filename scheme, extracts tags (via TagGuesser) from filename and fills the appropriate fields on TagDialog. void TagDialog::guessFromFilename() //SLOT { TagGuesserDialog dialog( m_currentTrack->playableUrl().path(), this ); if( dialog.exec() == QDialog::Accepted ) { dialog.onAccept(); int cur = 0; QMap tags = dialog.guessedTags(); if( !tags.isEmpty() ) { if( tags.contains( Meta::valTitle ) ) ui->kLineEdit_title->setText( tags[Meta::valTitle] ); if( tags.contains( Meta::valArtist ) ) { cur = ui->kComboBox_artist->currentIndex(); ui->kComboBox_artist->setItemText( cur, tags[Meta::valArtist] ); } if( tags.contains( Meta::valAlbum ) ) { cur = ui->kComboBox_album->currentIndex(); ui->kComboBox_album->setItemText( cur, tags[Meta::valAlbum] ); } if( tags.contains( Meta::valAlbumArtist ) ) { cur = ui->kComboBox_albumArtist->currentIndex(); ui->kComboBox_albumArtist->setItemText( cur, tags[Meta::valAlbumArtist] ); } if( tags.contains( Meta::valTrackNr ) ) ui->qSpinBox_track->setValue( tags[Meta::valTrackNr].toInt() ); if( tags.contains( Meta::valComment ) ) ui->qPlainTextEdit_comment->setPlainText( tags[Meta::valComment] ); if( tags.contains( Meta::valYear ) ) ui->qSpinBox_year->setValue( tags[Meta::valYear].toInt() ); if( tags.contains( Meta::valComposer ) ) { cur = ui->kComboBox_composer->currentIndex(); ui->kComboBox_composer->setItemText( cur, tags[Meta::valComposer] ); } if( tags.contains( Meta::valGenre ) ) { cur = ui->kComboBox_genre->currentIndex(); ui->kComboBox_genre->setItemText( cur, tags[Meta::valGenre] ); } if( tags.contains( Meta::valDiscNr ) ) { ui->qSpinBox_discNumber->setValue( tags[Meta::valDiscNr].toInt() ); } } else { debug() << "guessing tags from filename failed" << endl; } } } //////////////////////////////////////////////////////////////////////////////// // PRIVATE //////////////////////////////////////////////////////////////////////////////// void TagDialog::initUi() { DEBUG_BLOCK // delete itself when closing setAttribute( Qt::WA_DeleteOnClose ); KConfigGroup config = Amarok::config( "TagDialog" ); ui->qTabWidget->addTab( ui->summaryTab , i18n( "Summary" ) ); ui->qTabWidget->addTab( ui->tagsTab , i18n( "Tags" ) ); ui->qTabWidget->addTab( ui->lyricsTab , i18n( "Lyrics" ) ); ui->qTabWidget->addTab( ui->labelsTab , i18n( "Labels" ) ); ui->kComboBox_label->completionObject()->setIgnoreCase( true ); ui->kComboBox_label->setCompletionMode( KCompletion::CompletionPopup ); m_labelModel = new LabelListModel( QStringList(), this ); ui->labelsList->setModel( m_labelModel ); ui->labelsTab->setEnabled( true ); ui->qTabWidget->setCurrentIndex( config.readEntry( "CurrentTab", 0 ) ); ui->kComboBox_artist->completionObject()->setIgnoreCase( true ); ui->kComboBox_artist->setCompletionMode( KCompletion::CompletionPopup ); ui->kComboBox_album->completionObject()->setIgnoreCase( true ); ui->kComboBox_album->setCompletionMode( KCompletion::CompletionPopup ); ui->kComboBox_albumArtist->completionObject()->setIgnoreCase( true ); ui->kComboBox_albumArtist->setCompletionMode( KCompletion::CompletionPopup ); ui->kComboBox_composer->completionObject()->setIgnoreCase( true ); ui->kComboBox_composer->setCompletionMode( KCompletion::CompletionPopup ); ui->kComboBox_genre->completionObject()->setIgnoreCase( true ); ui->kComboBox_genre->setCompletionMode( KCompletion::CompletionPopup ); ui->kComboBox_label->completionObject()->setIgnoreCase( true ); ui->kComboBox_label->setCompletionMode( KCompletion::CompletionPopup ); ui->addButton->setEnabled( false ); ui->removeButton->setEnabled( false ); // set an icon for the open-in-konqui button ui->pushButton_open->setIcon( QIcon::fromTheme( "folder-amarok" ) ); connect( ui->pushButton_guessTags, &QAbstractButton::clicked, this, &TagDialog::guessFromFilename ); // Connects for modification check // only set to overwrite-on-save if the text has changed connect( ui->kLineEdit_title, &QLineEdit::textChanged, this, &TagDialog::checkChanged ); connect( ui->kComboBox_composer, QOverload::of(&QComboBox::activated), this, &TagDialog::checkChanged ); connect( ui->kComboBox_composer, &QComboBox::editTextChanged, this, &TagDialog::checkChanged ); connect( ui->kComboBox_artist, QOverload::of(&QComboBox::activated), this, &TagDialog::checkChanged ); connect( ui->kComboBox_artist, &QComboBox::editTextChanged, this, &TagDialog::checkChanged ); connect( ui->kComboBox_album, QOverload::of(&QComboBox::activated), this, &TagDialog::checkChanged ); connect( ui->kComboBox_album, &QComboBox::editTextChanged, this, &TagDialog::checkChanged ); connect( ui->kComboBox_albumArtist, QOverload::of(&QComboBox::activated), this, &TagDialog::checkChanged ); connect( ui->kComboBox_albumArtist, &QComboBox::editTextChanged, this, &TagDialog::checkChanged ); connect( ui->kComboBox_genre, QOverload::of(&QComboBox::activated), this, &TagDialog::checkChanged ); connect( ui->kComboBox_genre, &QComboBox::editTextChanged, this, &TagDialog::checkChanged ); connect( ui->kLineEdit_Bpm, &QLineEdit::textChanged, this, &TagDialog::checkChanged ); connect( ui->ratingWidget, QOverload::of(&KRatingWidget::ratingChanged), this, &TagDialog::checkChanged ); connect( ui->qSpinBox_track, QOverload::of(&QSpinBox::valueChanged), this, &TagDialog::checkChanged ); connect( ui->qSpinBox_year, QOverload::of(&QSpinBox::valueChanged), this, &TagDialog::checkChanged ); connect( ui->qSpinBox_score, QOverload::of(&QSpinBox::valueChanged), this, &TagDialog::checkChanged ); connect( ui->qPlainTextEdit_comment, &QPlainTextEdit::textChanged, this, &TagDialog::checkChanged ); connect( ui->kRichTextEdit_lyrics, &QTextEdit::textChanged, this, &TagDialog::checkChanged ); connect( ui->qSpinBox_discNumber, QOverload::of(&QSpinBox::valueChanged), this, &TagDialog::checkChanged ); connect( ui->pushButton_cancel, &QAbstractButton::clicked, this, &TagDialog::cancelPressed ); connect( ui->pushButton_ok, &QAbstractButton::clicked, this, &TagDialog::accept ); connect( ui->pushButton_open, &QAbstractButton::clicked, this, &TagDialog::openPressed ); connect( ui->pushButton_previous, &QAbstractButton::clicked, this, &TagDialog::previousTrack ); connect( ui->pushButton_next, &QAbstractButton::clicked, this, &TagDialog::nextTrack ); connect( ui->checkBox_perTrack, &QCheckBox::toggled, this, &TagDialog::perTrack ); connect( ui->addButton, &QAbstractButton::clicked, this, &TagDialog::addLabelPressed ); connect( ui->removeButton, &QAbstractButton::clicked, this, &TagDialog::removeLabelPressed ); connect( ui->kComboBox_label, QOverload::of(&KComboBox::activated), this, &TagDialog::labelModified ); connect( ui->kComboBox_label, &QComboBox::editTextChanged, this, &TagDialog::labelModified ); connect( ui->kComboBox_label, QOverload<>::of(&KComboBox::returnPressed), this, &TagDialog::addLabelPressed ); connect( ui->kComboBox_label, QOverload<>::of(&KComboBox::returnPressed), this, &TagDialog::checkChanged ); connect( ui->labelsList, &QListView::pressed, this, &TagDialog::labelSelected ); ui->pixmap_cover->setContextMenuPolicy( Qt::CustomContextMenu ); connect( ui->pixmap_cover, &CoverLabel::customContextMenuRequested, this, &TagDialog::showCoverMenu ); connect( ui->pushButton_musicbrainz, &QAbstractButton::clicked, this, &TagDialog::musicbrainzTagger ); if( m_tracks.count() > 1 ) setPerTrack( false ); else setPerTrack( true ); ui->pushButton_ok->setEnabled( false ); startDataQueries(); } void TagDialog::setCurrentTrack( int num ) { if( num < 0 || num >= m_tracks.count() ) return; if( m_currentTrack ) // even in multiple tracks mode we don't want to write back setTagsToTrack(); // there is a logical problem here. // if the track itself changes (e.g. because it get's a new album) // then we don't re-subscribe - if( m_currentTrack && m_currentTrack->album() ) - unsubscribeFrom( m_currentTrack->album() ); + if( m_currentAlbum ) + unsubscribeFrom( m_currentAlbum ); m_currentTrack = m_tracks.at( num ); + m_currentAlbum = m_currentTrack->album(); m_currentTrackNum = num; - if( m_currentTrack && m_currentTrack->album() ) - subscribeTo( m_currentTrack->album() ); + if( m_currentAlbum ) + subscribeTo( m_currentAlbum ); setControlsAccessability(); updateButtons(); setTagsToUi(); } void TagDialog::startDataQuery( Collections::QueryMaker::QueryType type, const QMetaMethod &signal, const QMetaMethod &slot ) { Collections::QueryMaker *qm = CollectionManager::instance()->queryMaker(); qm->setQueryType( type ); connect( qm, &Collections::QueryMaker::queryDone, this, &TagDialog::dataQueryDone, Qt::QueuedConnection ); connect( qm, signal, this, slot, Qt::QueuedConnection ); qm->setAutoDelete( true ); qm->run(); } void TagDialog::startDataQueries() { startDataQuery( Collections::QueryMaker::Artist, QMetaMethod::fromSignal(&Collections::QueryMaker::newArtistsReady), QMetaMethod::fromSignal(&TagDialog::artistsReady) ); startDataQuery( Collections::QueryMaker::Album, QMetaMethod::fromSignal(&Collections::QueryMaker::newAlbumsReady), QMetaMethod::fromSignal(&TagDialog::albumsReady) ); startDataQuery( Collections::QueryMaker::Composer, QMetaMethod::fromSignal(&Collections::QueryMaker::newComposersReady), QMetaMethod::fromSignal(&TagDialog::composersReady) ); startDataQuery( Collections::QueryMaker::Genre, QMetaMethod::fromSignal(&Collections::QueryMaker::newGenresReady), QMetaMethod::fromSignal(&TagDialog::genresReady) ); startDataQuery( Collections::QueryMaker::Label, QMetaMethod::fromSignal(&Collections::QueryMaker::newLabelsReady), QMetaMethod::fromSignal(&TagDialog::labelsReady) ); } inline const QString TagDialog::unknownSafe( const QString &s ) const { return ( s.isNull() || s.isEmpty() || s == "?" || s == "-" ) ? i18nc( "The value for this tag is not known", "Unknown" ) : s; } inline const QString TagDialog::unknownSafe( int i ) const { return ( i == 0 ) ? i18nc( "The value for this tag is not known", "Unknown" ) : QString::number( i ); } void TagDialog::showCoverMenu( const QPoint &pos ) { - Meta::AlbumPtr album = m_currentTrack->album(); - if( !album ) + if( !m_currentAlbum ) return; // TODO: warning or something? - QAction *displayCoverAction = new DisplayCoverAction( this, album ); - QAction *unsetCoverAction = new UnsetCoverAction( this, album ); + QAction *displayCoverAction = new DisplayCoverAction( this, m_currentAlbum ); + QAction *unsetCoverAction = new UnsetCoverAction( this, m_currentAlbum ); - if( !album->hasImage() ) + if( !m_currentAlbum->hasImage() ) { displayCoverAction->setEnabled( false ); unsetCoverAction->setEnabled( false ); } QMenu *menu = new QMenu( this ); menu->addAction( displayCoverAction ); - menu->addAction( new FetchCoverAction( this, album ) ); - menu->addAction( new SetCustomCoverAction( this, album ) ); + menu->addAction( new FetchCoverAction( this, m_currentAlbum ) ); + menu->addAction( new SetCustomCoverAction( this, m_currentAlbum ) ); menu->addAction( unsetCoverAction ); menu->exec( ui->pixmap_cover->mapToGlobal(pos) ); } void TagDialog::setTagsToUi( const QVariantMap &tags ) { bool oldChanged = m_changed; // -- the windows title if( m_perTrack ) { setWindowTitle( i18n("Track Details: %1 by %2", m_currentTrack->name(), m_currentTrack->artist() ? m_currentTrack->artist()->name() : QString() ) ); } else { setWindowTitle( i18ncp( "The amount of tracks being edited", "1 Track", "Information for %1 Tracks", m_tracks.count() ) ); } // -- the title in the summary tab if( m_perTrack ) { QString niceTitle; const QFontMetrics fnt = ui->trackArtistAlbumLabel->fontMetrics(); const int len = ui->trackArtistAlbumLabel->width(); QString curTrackAlbName; QString curArtistName; QString curTrackName = fnt.elidedText( m_currentTrack->name().toHtmlEscaped(), Qt::ElideRight, len ); QString curTrackPretName = fnt.elidedText( m_currentTrack->prettyName().toHtmlEscaped(), Qt::ElideRight, len ); - if( m_currentTrack->album() ) - curTrackAlbName = fnt.elidedText( m_currentTrack->album()->name().toHtmlEscaped(), Qt::ElideRight, len ); + if( m_currentAlbum ) + curTrackAlbName = fnt.elidedText( m_currentAlbum->name().toHtmlEscaped(), Qt::ElideRight, len ); if( m_currentTrack->artist() ) curArtistName = fnt.elidedText( m_currentTrack->artist()->name().toHtmlEscaped(), Qt::ElideRight, len ); - if( m_currentTrack->album() && m_currentTrack->album()->name().isEmpty() ) + if( m_currentAlbum && m_currentAlbum->name().isEmpty() ) { if( !m_currentTrack->name().isEmpty() ) { if( !m_currentTrack->artist()->name().isEmpty() ) niceTitle = i18n( "%1 by %2", curTrackName, curArtistName ); else niceTitle = i18n( "%1", curTrackName ); } else niceTitle = curTrackPretName; } - else if( m_currentTrack->album() ) + else if( m_currentAlbum ) niceTitle = i18n( "%1 by %2 on %3" , curTrackName, curArtistName, curTrackAlbName ); else if( m_currentTrack->artist() ) niceTitle = i18n( "%1 by %2" , curTrackName, curArtistName ); else niceTitle = i18n( "%1" , curTrackName ); ui->trackArtistAlbumLabel->setText( niceTitle ); } else { ui->trackArtistAlbumLabel->setText( i18np( "Editing 1 file", "Editing %1 files", m_tracks.count() ) ); } // -- the rest ui->kLineEdit_title->setText( tags.value( Meta::Field::TITLE ).toString() ); selectOrInsertText( tags.value( Meta::Field::ARTIST ).toString(), ui->kComboBox_artist ); selectOrInsertText( tags.value( Meta::Field::ALBUM ).toString(), ui->kComboBox_album ); selectOrInsertText( tags.value( Meta::Field::ALBUMARTIST ).toString(), ui->kComboBox_albumArtist ); selectOrInsertText( tags.value( Meta::Field::COMPOSER ).toString(), ui->kComboBox_composer ); ui->qPlainTextEdit_comment->setPlainText( tags.value( Meta::Field::COMMENT ).toString() ); selectOrInsertText( tags.value( Meta::Field::GENRE ).toString(), ui->kComboBox_genre ); ui->qSpinBox_track->setValue( tags.value( Meta::Field::TRACKNUMBER ).toInt() ); ui->qSpinBox_discNumber->setValue( tags.value( Meta::Field::DISCNUMBER ).toInt() ); ui->qSpinBox_year->setValue( tags.value( Meta::Field::YEAR ).toInt() ); ui->kLineEdit_Bpm->setText( tags.value( Meta::Field::BPM ).toString() ); ui->qLabel_length->setText( unknownSafe( Meta::msToPrettyTime( tags.value( Meta::Field::LENGTH ).toLongLong() ) ) ); ui->qLabel_bitrate->setText( Meta::prettyBitrate( tags.value( Meta::Field::BITRATE ).toInt() ) ); ui->qLabel_samplerate->setText( unknownSafe( tags.value( Meta::Field::SAMPLERATE ).toInt() ) ); ui->qLabel_size->setText( Meta::prettyFilesize( tags.value( Meta::Field::FILESIZE ).toLongLong() ) ); ui->qLabel_format->setText( unknownSafe( tags.value( Meta::Field::TYPE ).toString() ) ); ui->qSpinBox_score->setValue( tags.value( Meta::Field::SCORE ).toInt() ); ui->ratingWidget->setRating( tags.value( Meta::Field::RATING ).toInt() ); ui->ratingWidget->setMaxRating( 10 ); int playcount = tags.value( Meta::Field::PLAYCOUNT ).toInt(); ui->qLabel_playcount->setText( unknownSafe( playcount ) ); QDateTime firstPlayed = tags.value( Meta::Field::FIRST_PLAYED ).toDateTime(); ui->qLabel_firstPlayed->setText( Amarok::verboseTimeSince( firstPlayed ) ); QDateTime lastPlayed = tags.value( Meta::Field::LAST_PLAYED ).toDateTime(); ui->qLabel_lastPlayed->setText( Amarok::verboseTimeSince( lastPlayed ) ); ui->qLabel_collection->setText( tags.contains( Meta::Field::COLLECTION ) ? tags.value( Meta::Field::COLLECTION ).toString() : i18nc( "The collection this track is part of", "None") ); // special handling - we want to hide this if empty if( tags.contains( Meta::Field::NOTE ) ) { ui->noteLabel->show(); ui->qLabel_note->setText( tags.value( Meta::Field::NOTE ).toString() ); ui->qLabel_note->show(); } else { ui->noteLabel->hide(); ui->qLabel_note->hide(); } ui->kRichTextEdit_lyrics->setTextOrHtml( tags.value( Meta::Field::LYRICS ).toString() ); m_labelModel->setLabels( tags.value( Meta::Field::LABELS ).toStringList() ); ui->labelsList->update(); updateCover(); setControlsAccessability(); // If it's a local file, write the directory to m_path, else disable the "open in konqui" button QString urlString = tags.value( Meta::Field::URL ).toString(); QUrl url( urlString ); //pathOrUrl will give localpath or proper url for remote. ui->kLineEdit_location->setText( url.toDisplayString() ); if( url.isLocalFile() ) { ui->locationLabel->show(); ui->kLineEdit_location->show(); QFileInfo fi( urlString ); m_path = fi.isDir() ? urlString : url.adjusted(QUrl::RemoveFilename).path(); ui->pushButton_open->setEnabled( true ); } else { m_path.clear(); ui->pushButton_open->setEnabled( false ); } m_changed = oldChanged; ui->pushButton_ok->setEnabled( m_changed ); } void TagDialog::setTagsToUi() { if( m_perTrack ) setTagsToUi( m_storedTags.value( m_currentTrack ) ); else setTagsToUi( getTagsFromMultipleTracks() ); } QVariantMap TagDialog::getTagsFromUi( const QVariantMap &tags ) const { QVariantMap map; if( ui->kLineEdit_title->text() != tags.value( Meta::Field::TITLE ).toString() ) map.insert( Meta::Field::TITLE, ui->kLineEdit_title->text() ); if( ui->kComboBox_artist->currentText() != tags.value( Meta::Field::ARTIST ).toString() ) map.insert( Meta::Field::ARTIST, ui->kComboBox_artist->currentText() ); if( ui->kComboBox_album->currentText() != tags.value( Meta::Field::ALBUM ).toString() ) map.insert( Meta::Field::ALBUM, ui->kComboBox_album->currentText() ); if( ui->kComboBox_albumArtist->currentText() != tags.value( Meta::Field::ALBUMARTIST ).toString() ) map.insert( Meta::Field::ALBUMARTIST, ui->kComboBox_albumArtist->currentText() ); if( ui->kComboBox_composer->currentText() != tags.value( Meta::Field::COMPOSER ).toString() ) map.insert( Meta::Field::COMPOSER, ui->kComboBox_composer->currentText() ); if( ui->qPlainTextEdit_comment->toPlainText() != tags.value( Meta::Field::COMMENT ).toString() ) map.insert( Meta::Field::COMMENT, ui->qPlainTextEdit_comment->toPlainText() ); if( ui->kComboBox_genre->currentText() != tags.value( Meta::Field::GENRE ).toString() ) map.insert( Meta::Field::GENRE, ui->kComboBox_genre->currentText() ); if( ui->qSpinBox_track->value() != tags.value( Meta::Field::TRACKNUMBER ).toInt() ) map.insert( Meta::Field::TRACKNUMBER, ui->qSpinBox_track->value() ); if( ui->qSpinBox_discNumber->value() != tags.value( Meta::Field::DISCNUMBER ).toInt() ) map.insert( Meta::Field::DISCNUMBER, ui->qSpinBox_discNumber->value() ); if( ui->kLineEdit_Bpm->text().toDouble() != tags.value( Meta::Field::BPM ).toReal() ) map.insert( Meta::Field::BPM, ui->kLineEdit_Bpm->text() ); if( ui->qSpinBox_year->value() != tags.value( Meta::Field::YEAR ).toInt() ) map.insert( Meta::Field::YEAR, ui->qSpinBox_year->value() ); if( ui->qSpinBox_score->value() != tags.value( Meta::Field::SCORE ).toInt() ) map.insert( Meta::Field::SCORE, ui->qSpinBox_score->value() ); if( ui->ratingWidget->rating() != tags.value( Meta::Field::RATING ).toUInt() ) map.insert( Meta::Field::RATING, ui->ratingWidget->rating() ); if( !m_tracks.count() || m_perTrack ) { //ignore these on MultipleTracksMode if ( ui->kRichTextEdit_lyrics->textOrHtml() != tags.value( Meta::Field::LYRICS ).toString() ) map.insert( Meta::Field::LYRICS, ui->kRichTextEdit_lyrics->textOrHtml() ); } QSet uiLabels = m_labelModel->labels().toSet(); QSet oldLabels = tags.value( Meta::Field::LABELS ).toStringList().toSet(); if( uiLabels != oldLabels ) map.insert( Meta::Field::LABELS, QVariant( uiLabels.toList() ) ); return map; } QVariantMap TagDialog::getTagsFromTrack( const Meta::TrackPtr &track ) const { QVariantMap map; if( !track ) return map; // get the shared pointers now to ensure that they don't get freed Meta::AlbumPtr album = track->album(); Meta::ArtistPtr artist = track->artist(); Meta::GenrePtr genre = track->genre(); Meta::ComposerPtr composer = track->composer(); Meta::YearPtr year = track->year(); if( !track->name().isEmpty() ) map.insert( Meta::Field::TITLE, track->name() ); if( artist && !artist->name().isEmpty() ) map.insert( Meta::Field::ARTIST, artist->name() ); if( album && !track->album()->name().isEmpty() ) { map.insert( Meta::Field::ALBUM, album->name() ); if( album->hasAlbumArtist() && !album->albumArtist()->name().isEmpty() ) map.insert( Meta::Field::ALBUMARTIST, album->albumArtist()->name() ); } if( composer && !composer->name().isEmpty() ) map.insert( Meta::Field::COMPOSER, composer->name() ); if( !track->comment().isEmpty() ) map.insert( Meta::Field::COMMENT, track->comment() ); if( genre && !genre->name().isEmpty() ) map.insert( Meta::Field::GENRE, genre->name() ); if( track->trackNumber() ) map.insert( Meta::Field::TRACKNUMBER, track->trackNumber() ); if( track->discNumber() ) map.insert( Meta::Field::DISCNUMBER, track->discNumber() ); if( year && year->year() ) map.insert( Meta::Field::YEAR, year->year() ); if( track->bpm() > 0.0) map.insert( Meta::Field::BPM, track->bpm() ); if( track->length() ) map.insert( Meta::Field::LENGTH, track->length() ); if( track->bitrate() ) map.insert( Meta::Field::BITRATE, track->bitrate() ); if( track->sampleRate() ) map.insert( Meta::Field::SAMPLERATE, track->sampleRate() ); if( track->filesize() ) map.insert( Meta::Field::FILESIZE, track->filesize() ); Meta::ConstStatisticsPtr statistics = track->statistics(); map.insert( Meta::Field::SCORE, statistics->score() ); map.insert( Meta::Field::RATING, statistics->rating() ); map.insert( Meta::Field::PLAYCOUNT, statistics->playCount() ); map.insert( Meta::Field::FIRST_PLAYED, statistics->firstPlayed() ); map.insert( Meta::Field::LAST_PLAYED, statistics->lastPlayed() ); map.insert( Meta::Field::URL, track->prettyUrl() ); map.insert( Meta::Field::TYPE, track->type() ); if( track->inCollection() ) map.insert( Meta::Field::COLLECTION, track->collection()->prettyName() ); if( !track->notPlayableReason().isEmpty() ) map.insert( Meta::Field::NOTE, i18n( "The track is not playable. %1", track->notPlayableReason() ) ); QStringList labelNames; foreach( const Meta::LabelPtr &label, track->labels() ) { labelNames << label->name(); } map.insert( Meta::Field::LABELS, labelNames ); map.insert( Meta::Field::LYRICS, track->cachedLyrics() ); return map; } QVariantMap TagDialog::getTagsFromMultipleTracks() const { QVariantMap map; if( m_tracks.isEmpty() ) return map; //Check which fields are the same for all selected tracks QSet mismatchingTags; Meta::TrackPtr first = m_tracks.first(); map = getTagsFromTrack( first ); QString directory = first->playableUrl().adjusted(QUrl::RemoveFilename).path(); int scoreCount = 0; double scoreSum = map.value( Meta::Field::SCORE ).toDouble(); if( map.value( Meta::Field::SCORE ).toDouble() ) scoreCount++; int ratingCount = 0; int ratingSum = map.value( Meta::Field::RATING ).toInt(); if( map.value( Meta::Field::RATING ).toInt() ) ratingCount++; QDateTime firstPlayed = first->statistics()->firstPlayed(); QDateTime lastPlayed = first->statistics()->lastPlayed(); qint64 length = first->length(); qint64 size = first->filesize(); QStringList validLabels = map.value( Meta::Field::LABELS ).toStringList(); for( int i = 1; i < m_tracks.count(); i++ ) { Meta::TrackPtr track = m_tracks[i]; QVariantMap tags = m_storedTags.value( track ); // -- figure out which tags do not match. // - occur not in every file mismatchingTags |= map.keys().toSet() - tags.keys().toSet(); mismatchingTags |= tags.keys().toSet() - map.keys().toSet(); // - not the same in every file foreach( const QString &key, (map.keys().toSet() & tags.keys().toSet()) ) { if( map.value( key ) != tags.value( key ) ) mismatchingTags.insert( key ); } // -- special handling for values // go up in the directories until we find a common one QString newDirectory = track->playableUrl().adjusted(QUrl::RemoveFilename).path(); while( newDirectory != directory ) { if( newDirectory.length() > directory.length() ) { QDir up( newDirectory ); up.cdUp(); QString d = up.path(); if( d == newDirectory ) // nothing changed { directory.clear(); break; } newDirectory = d; } else { QDir up( directory ); up.cdUp(); QString d = up.path(); if( d == directory ) // nothing changed { directory.clear(); break; } directory = d; } } if( !track->playableUrl().isLocalFile() ) directory.clear(); // score and rating (unrated if rating == 0) scoreSum += tags.value( Meta::Field::SCORE ).toDouble(); if( tags.value( Meta::Field::SCORE ).toDouble() ) scoreCount++; ratingSum += tags.value( Meta::Field::RATING ).toInt(); if( tags.value( Meta::Field::RATING ).toInt() ) ratingCount++; Meta::StatisticsPtr statistics = track->statistics(); if( statistics->firstPlayed().isValid() && (!firstPlayed.isValid() || statistics->firstPlayed() < firstPlayed) ) firstPlayed = statistics->firstPlayed(); if( statistics->lastPlayed().isValid() && (!lastPlayed.isValid() || statistics->lastPlayed() > lastPlayed) ) lastPlayed = statistics->lastPlayed(); length += track->length(); size += track->filesize(); // Only show labels present in all of the tracks QStringList labels = tags.value( Meta::Field::LABELS ).toStringList(); for ( int x = 0; x < validLabels.count(); x++ ) { if ( !labels.contains( validLabels.at( x ) ) ) validLabels.removeAt( x ); } } foreach( const QString &key, mismatchingTags ) map.remove( key ); map.insert( Meta::Field::URL, directory ); if( scoreCount > 0 ) map.insert( Meta::Field::SCORE, scoreSum / scoreCount ); if( ratingCount > 0 ) // the extra fuzz is for emulating rounding to nearest integer map.insert( Meta::Field::RATING, ( ratingSum + ratingCount / 2 ) / ratingCount ); map.insert( Meta::Field::FIRST_PLAYED, firstPlayed ); map.insert( Meta::Field::LAST_PLAYED, lastPlayed ); map.insert( Meta::Field::LENGTH, length ); map.insert( Meta::Field::FILESIZE, size ); map.insert( Meta::Field::LABELS, validLabels ); return map; } void TagDialog::setTagsToTrack( const Meta::TrackPtr &track, const QVariantMap &tags ) { foreach( const QString &key, tags.keys() ) { m_storedTags[ track ].insert( key, tags.value( key ) ); } } void TagDialog::setTagsToMultipleTracks( QVariantMap tags ) { tags.remove( Meta::Field::LABELS ); foreach( const Meta::TrackPtr &track, m_tracks ) { setTagsToTrack( track, tags ); } } void TagDialog::setTagsToTrack() { QVariantMap oldTags; if( m_perTrack ) oldTags = m_storedTags.value( m_currentTrack ); else oldTags = getTagsFromMultipleTracks(); QVariantMap newTags = getTagsFromUi( oldTags ); if( !newTags.isEmpty() ) { m_changed = true; if( m_perTrack ) setTagsToTrack( m_currentTrack, newTags ); else { setTagsToMultipleTracks( newTags ); // -- special handling for labels if( newTags.contains( Meta::Field::LABELS ) ) { // determine the differences QSet oldLabelsSet = oldTags.value( Meta::Field::LABELS ).toStringList().toSet(); QSet newLabelsSet = newTags.value( Meta::Field::LABELS ).toStringList().toSet(); QSet labelsToRemove = oldLabelsSet - newLabelsSet; QSet labelsToAdd = newLabelsSet - oldLabelsSet; // apply the differences for each track foreach( const Meta::TrackPtr &track, m_tracks ) { QSet labelsSet = m_storedTags[track].value( Meta::Field::LABELS ).toStringList().toSet(); labelsSet += labelsToAdd; labelsSet -= labelsToRemove; m_storedTags[ track ].insert( Meta::Field::LABELS, QVariant( labelsSet.toList() ) ); } } } } } void TagDialog::setPerTrack( bool isEnabled ) { debug() << "setPerTrack" << m_tracks.count() << isEnabled; if( m_tracks.count() < 2 ) isEnabled = true; /* force an update so that we can use this function in the initialization if( m_perTrack == isEnabled ) return; */ m_perTrack = isEnabled; setControlsAccessability(); updateButtons(); } void TagDialog::updateButtons() { ui->pushButton_ok->setEnabled( m_changed ); ui->checkBox_perTrack->setVisible( m_tracks.count() > 1 ); ui->pushButton_previous->setVisible( m_tracks.count() > 1 ); ui->pushButton_next->setVisible( m_tracks.count() > 1 ); ui->checkBox_perTrack->setChecked( m_perTrack ); ui->pushButton_previous->setEnabled( m_perTrack && m_currentTrackNum > 0 ); ui->pushButton_next->setEnabled( m_perTrack && m_currentTrackNum < m_tracks.count()-1 ); } void TagDialog::updateCover() { DEBUG_BLOCK if( !m_currentTrack ) return; // -- get the album - Meta::AlbumPtr album = m_currentTrack->album(); + Meta::AlbumPtr album = m_currentAlbum; if( !m_perTrack ) { foreach( Meta::TrackPtr track, m_tracks ) { if( track->album() != album ) - album = 0; + album = nullptr; } } // -- set the ui const int s = 100; // Image preview size ui->pixmap_cover->setMinimumSize( s, s ); ui->pixmap_cover->setMaximumSize( s, s ); if( !album ) { ui->pixmap_cover->setVisible( false ); } else { ui->pixmap_cover->setVisible( true ); ui->pixmap_cover->setPixmap( The::svgHandler()->imageWithBorder( album, s ) ); QString artist = m_currentTrack->artist() ? m_currentTrack->artist()->name() : QString(); ui->pixmap_cover->setInformation( artist, album->name() ); } } void TagDialog::setControlsAccessability() { bool editable = m_currentTrack ? bool( m_currentTrack->editor() ) : true; ui->qTabWidget->setTabEnabled( ui->qTabWidget->indexOf(ui->lyricsTab), m_perTrack ); ui->kLineEdit_title->setEnabled( m_perTrack && editable ); ui->kLineEdit_title->setClearButtonEnabled( m_perTrack && editable ); #define enableOrDisable( X ) \ ui->X->setEnabled( editable ); \ qobject_cast(ui->X->lineEdit())->setClearButtonEnabled( editable ) enableOrDisable( kComboBox_artist ); enableOrDisable( kComboBox_albumArtist ); enableOrDisable( kComboBox_composer ); enableOrDisable( kComboBox_album ); enableOrDisable( kComboBox_genre ); #undef enableOrDisable ui->qSpinBox_track->setEnabled( m_perTrack && editable ); ui->qSpinBox_discNumber->setEnabled( editable ); ui->qSpinBox_year->setEnabled( editable ); ui->kLineEdit_Bpm->setEnabled( editable ); ui->kLineEdit_Bpm->setClearButtonEnabled( editable ); ui->qPlainTextEdit_comment->setEnabled( editable ); ui->pushButton_guessTags->setEnabled( m_perTrack && editable ); ui->pushButton_musicbrainz->setEnabled( editable ); } -void -TagDialog::saveLabels( Meta::TrackPtr track, const QStringList &labels ) -{ - if( !track ) - return; - - QHash labelMap; - foreach( const Meta::LabelPtr &label, track->labels() ) - { - labelMap.insert( label->name(), label ); - } - - // labels to remove - foreach( const QString &label, labelMap.keys().toSet() - labels.toSet() ) - { - track->removeLabel( labelMap.value( label ) ); - } - - // labels to add - foreach( const QString &label, labels.toSet() - labelMap.keys().toSet() ) - { - track->addLabel( label ); - } -} - - void TagDialog::saveTags() { setTagsToTrack(); - foreach( Meta::TrackPtr track, m_tracks ) + for( auto &track : m_tracks ) { QVariantMap data = m_storedTags[ track ]; //there is really no need to write to the file if only info m_stored in the db has changed if( !data.isEmpty() ) { debug() << "File info changed...."; - if( data.contains( Meta::Field::SCORE ) ) - track->statistics()->setScore( data.value( Meta::Field::SCORE ).toInt() ); - if( data.contains( Meta::Field::RATING ) ) - track->statistics()->setRating( data.value( Meta::Field::RATING ).toInt() ); - if( data.contains( Meta::Field::LYRICS ) ) + auto lambda = [=] () mutable { - track->setCachedLyrics( data.value( Meta::Field::LYRICS ).toString() ); - emit lyricsChanged( track->uidUrl() ); - } - - saveLabels( track, data.value( Meta::Field::LABELS ).toStringList() ); - - Meta::TrackEditorPtr ec = track->editor(); - if( !ec ) - { - debug() << "Track" << track->prettyUrl() << "does not have Meta::TrackEditor. Skipping."; - continue; - } + if( data.contains( Meta::Field::SCORE ) ) + track->statistics()->setScore( data.value( Meta::Field::SCORE ).toInt() ); + if( data.contains( Meta::Field::RATING ) ) + track->statistics()->setRating( data.value( Meta::Field::RATING ).toInt() ); + if( data.contains( Meta::Field::LYRICS ) ) + track->setCachedLyrics( data.value( Meta::Field::LYRICS ).toString() ); + + QStringList labels = data.value( Meta::Field::LABELS ).toStringList(); + QHash labelMap; + for( const auto &label : track->labels() ) + labelMap.insert( label->name(), label ); + + // labels to remove + for( const auto &label : labelMap.keys().toSet() - labels.toSet() ) + track->removeLabel( labelMap.value( label ) ); + + // labels to add + for( const auto &label : labels.toSet() - labelMap.keys().toSet() ) + track->addLabel( label ); + + Meta::TrackEditorPtr ec = track->editor(); + if( !ec ) + { + debug() << "Track" << track->prettyUrl() << "does not have Meta::TrackEditor. Skipping."; + return; + } - ec->beginUpdate(); - if( data.contains( Meta::Field::TITLE ) ) - ec->setTitle( data.value( Meta::Field::TITLE ).toString() ); - if( data.contains( Meta::Field::COMMENT ) ) - ec->setComment( data.value( Meta::Field::COMMENT ).toString() ); - if( data.contains( Meta::Field::ARTIST ) ) - ec->setArtist( data.value( Meta::Field::ARTIST ).toString() ); - if( data.contains( Meta::Field::ALBUM ) ) - ec->setAlbum( data.value( Meta::Field::ALBUM ).toString() ); - if( data.contains( Meta::Field::GENRE ) ) - ec->setGenre( data.value( Meta::Field::GENRE ).toString() ); - if( data.contains( Meta::Field::COMPOSER ) ) - ec->setComposer( data.value( Meta::Field::COMPOSER ).toString() ); - if( data.contains( Meta::Field::YEAR ) ) - ec->setYear( data.value( Meta::Field::YEAR ).toInt() ); - if( data.contains( Meta::Field::TRACKNUMBER ) ) - ec->setTrackNumber( data.value( Meta::Field::TRACKNUMBER ).toInt() ); - if( data.contains( Meta::Field::DISCNUMBER ) ) - ec->setDiscNumber( data.value( Meta::Field::DISCNUMBER ).toInt() ); - if( data.contains( Meta::Field::BPM ) ) - ec->setBpm( data.value( Meta::Field::BPM ).toDouble() ); - if( data.contains( Meta::Field::ALBUMARTIST ) ) - ec->setAlbumArtist( data.value( Meta::Field::ALBUMARTIST ).toString() ); - - ec->endUpdate(); - // note: the track should by itself emit a collectionUpdated signal if needed + ec->beginUpdate(); + + if( data.contains( Meta::Field::TITLE ) ) + ec->setTitle( data.value( Meta::Field::TITLE ).toString() ); + if( data.contains( Meta::Field::COMMENT ) ) + ec->setComment( data.value( Meta::Field::COMMENT ).toString() ); + if( data.contains( Meta::Field::ARTIST ) ) + ec->setArtist( data.value( Meta::Field::ARTIST ).toString() ); + if( data.contains( Meta::Field::ALBUM ) ) + ec->setAlbum( data.value( Meta::Field::ALBUM ).toString() ); + if( data.contains( Meta::Field::GENRE ) ) + ec->setGenre( data.value( Meta::Field::GENRE ).toString() ); + if( data.contains( Meta::Field::COMPOSER ) ) + ec->setComposer( data.value( Meta::Field::COMPOSER ).toString() ); + if( data.contains( Meta::Field::YEAR ) ) + ec->setYear( data.value( Meta::Field::YEAR ).toInt() ); + if( data.contains( Meta::Field::TRACKNUMBER ) ) + ec->setTrackNumber( data.value( Meta::Field::TRACKNUMBER ).toInt() ); + if( data.contains( Meta::Field::DISCNUMBER ) ) + ec->setDiscNumber( data.value( Meta::Field::DISCNUMBER ).toInt() ); + if( data.contains( Meta::Field::BPM ) ) + ec->setBpm( data.value( Meta::Field::BPM ).toDouble() ); + if( data.contains( Meta::Field::ALBUMARTIST ) ) + ec->setAlbumArtist( data.value( Meta::Field::ALBUMARTIST ).toString() ); + + ec->endUpdate(); + // note: the track should by itself emit a collectionUpdated signal if needed + }; + std::thread thread( lambda ); + thread.detach(); } } } void TagDialog::selectOrInsertText( const QString &text, QComboBox *comboBox ) { int index = comboBox->findText( text ); if( index == -1 ) { comboBox->insertItem( 0, text ); //insert at the beginning comboBox->setCurrentIndex( 0 ); } else { comboBox->setCurrentIndex( index ); } } void TagDialog::musicbrainzTagger() { DEBUG_BLOCK MusicBrainzTagger *dialog = new MusicBrainzTagger( m_tracks, this ); dialog->setWindowTitle( i18n( "MusicBrainz Tagger" ) ); connect( dialog, &MusicBrainzTagger::sendResult, this, &TagDialog::musicbrainzTaggerResult ); dialog->show(); } void TagDialog::musicbrainzTaggerResult( const QMap result ) { if( result.isEmpty() ) return; foreach( Meta::TrackPtr track, result.keys() ) { setTagsToTrack( track, result.value( track ) ); } m_changed = true; if( m_perTrack ) setTagsToUi( m_storedTags.value( m_currentTrack ) ); else setTagsToUi( getTagsFromMultipleTracks() ); } diff --git a/src/dialogs/TagDialog.h b/src/dialogs/TagDialog.h index 872d0e950b..68678821cd 100644 --- a/src/dialogs/TagDialog.h +++ b/src/dialogs/TagDialog.h @@ -1,237 +1,228 @@ /**************************************************************************************** * Copyright (c) 2004 Mark Kretschmann * * Copyright (c) 2004 Pierpaolo Di Panfilo * * Copyright (c) 2005 Alexandre Pereira de Oliveira * * Copyright (c) 2008 Leo Franchi * * Copyright (c) 2011 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_TAGDIALOG_H #define AMAROK_TAGDIALOG_H #include #include "amarok_export.h" #include "playlist/PlaylistItem.h" #include "LabelListModel.h" #include "core/meta/Observer.h" #include "core/collections/MetaQueryMaker.h" #include #include #include #include #include #include #include #include namespace Ui { class TagDialogBase; } class QComboBox; class AMAROK_EXPORT TagDialog : public QDialog, public Meta::Observer { Q_OBJECT public: enum Tabs { SUMMARYTAB, TAGSTAB, LYRICSTAB, LABELSTAB }; explicit TagDialog( const Meta::TrackList &tracks, QWidget *parent = 0 ); explicit TagDialog( Meta::TrackPtr track, QWidget *parent = 0 ); explicit TagDialog( Collections::QueryMaker *qm ); ~TagDialog(); // inherited from Meta::Observer using Observer::metadataChanged; void metadataChanged( Meta::AlbumPtr album ); - Q_SIGNALS: - void lyricsChanged( const QString& ); - private Q_SLOTS: void accept(); void cancelPressed(); void openPressed(); void previousTrack(); void nextTrack(); void perTrack( bool ); void checkChanged(); /** * removes selected label from list */ void removeLabelPressed(); /** * adds label to list */ void addLabelPressed(); void showCoverMenu( const QPoint &pos ); /** * Shows FileNameLayoutDialog to guess tags from filename */ void guessFromFilename(); void musicbrainzTagger(); void musicbrainzTaggerResult( const QMap result ); /** Safely adds a track to m_tracks. Ensures that tracks are not added twice. */ void addTrack( Meta::TrackPtr &track ); void tracksReady( const Meta::TrackList &tracks ); void queryDone(); void albumsReady( const Meta::AlbumList &albums ); void artistsReady( const Meta::ArtistList &artists ); void composersReady( const Meta::ComposerList &composers ); void genresReady( const Meta::GenreList &genres ); /** * Updates global label list by querying all collections for all existing labels. */ void labelsReady( const Meta::LabelList &labels ); void dataQueryDone(); /** * Updates Add label button */ void labelModified(); /** * Updates Remove label button */ void labelSelected(); private: /** Sets some further properties and connects all the signals */ void initUi(); /** Set's the current track to the number. Will check agains invalid numbers, so the caller does not have to do that. */ void setCurrentTrack( int num ); /** Start a query maker for the given query type */ void startDataQuery( Collections::QueryMaker::QueryType type, const QMetaMethod &signal, const QMetaMethod &slot ); /** Start queries for artists, albums, composers, genres and labels to fill out the combo boxes */ void startDataQueries(); /** Sets the tags in the UI, cleaning unset tags */ void setTagsToUi( const QVariantMap &tags ); /** Sets the tags in the UI, cleaning unset tags depending on m_perTrack */ void setTagsToUi(); /** Gets the changed tags from the UI */ QVariantMap getTagsFromUi( const QVariantMap &tags ) const; /** Gets all the needed tags (just the one that we display or edit) from the track */ QVariantMap getTagsFromTrack( const Meta::TrackPtr &track ) const; /** Gets a summary of all the tags from m_tracks */ QVariantMap getTagsFromMultipleTracks() const; /** Overwrites all values in the stored tags with the new ones. Tags not in "tags" map are left unchanged. */ void setTagsToTrack( const Meta::TrackPtr &track, const QVariantMap &tags ); /** Overwrites all values in the stored tags with the new ones. Tags not in "tags" map are left unchanged. Exception are labels which are not set. */ void setTagsToMultipleTracks( QVariantMap tags ); /** Smartly writes back tags data depending on m_perTrack */ void setTagsToTrack(); /** Sets the UI to edit either one or the complete list of tracks. Don't forget to save the old ui values and set the new tags afterwards. */ void setPerTrack( bool isEnabled ); void updateButtons(); void updateCover(); void setControlsAccessability(); - /** - * Stores changes to labels for a specific track - * @arg track Track to store the labels to - * @arg labels The new set of labels for the track - */ - void saveLabels( Meta::TrackPtr track, const QStringList &labels ); - /** Writes all the tags to all the tracks. This finally updates the Meta::Tracks */ void saveTags(); /** * Returns "Unknown" if the value is null or not known * Otherwise returns the string */ const QString unknownSafe( const QString &s ) const; const QString unknownSafe( int i ) const; const QStringList filenameSchemes(); void selectOrInsertText( const QString &text, QComboBox *comboBox ); QString m_path; // the directory of the current track/tracks LabelListModel *m_labelModel; //!< Model MVC Class for Track label list bool m_perTrack; Meta::TrackList m_tracks; Meta::TrackPtr m_currentTrack; + Meta::AlbumPtr m_currentAlbum; int m_currentTrackNum; /** True if m_storedTags contains changed. The pushButton_ok will be activated if this one is true and the UI has further changes */ bool m_changed; /** The tags for the tracks. If the tags are edited then this structure is updated when switching between single and multiple mode or when pressing the save button. */ QMap m_storedTags; // the query maker to get the tracks to be edited Collections::QueryMaker *m_queryMaker; QSet m_artists; QSet m_albums; QSet m_albumArtists; QSet m_composers; QSet m_genres; QSet m_allLabels; //! all labels known to currently active collections, used for autocompletion Ui::TagDialogBase *ui; }; #endif /*AMAROK_TAGDIALOG_H*/ diff --git a/src/dynamic/BiasSolver.h b/src/dynamic/BiasSolver.h index 3896c912c5..8e7d9818ac 100644 --- a/src/dynamic/BiasSolver.h +++ b/src/dynamic/BiasSolver.h @@ -1,229 +1,229 @@ /**************************************************************************************** * Copyright (c) 2008 Daniel Caleb Jones * * Copyright (c) 2010, 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 . * ****************************************************************************************/ #ifndef AMAROK_BIASSOLVER_H #define AMAROK_BIASSOLVER_H #include "Bias.h" #include "core/meta/forward_declarations.h" #include "dynamic/TrackSet.h" #include #include #include #include namespace Dynamic { /** A playlist helper class */ class SolverList; /** * A class to implement the optimization algorithm used to generate * playlists from a set of biases. The method used here is slightly * complicated. It uses a a combination of heuristics, genetic algorithms, * and simulated annealing. The steps involved are documented in the source * code of BiasSolver::run. The whole operation is a little bit tricky since the bias solver runs as separate job without it's own event queue. The signals and slots are all handled via the UI threads event queue. Since the BiasSolver and all biases are created from the UI thread this is also the event queue of all objects. Bottom line: don't create objects in the bias solver that use signals. * Playlist generation is now done by adding tracks until we run out of time * or out of candidates. * We are back stepping a couple of times in such a case. * * The old bias solver that tried different optimization solutions is * not longer used. If you want to see the old code and/or re-use parts * of it see Amarok 2.7 * * To use the solver: * Create an instance * enqueue the job. When the job is finished, call solution to get the * playlist produced. */ class BiasSolver : public QObject, public ThreadWeaver::Job { Q_OBJECT public: /** * Create and prepare the solver. The constructor returns * * @param n The size of the playlist to generate. * @param biases The system of biases being applied. * @param context The tracks (if any) that precede the playlist * being generated. */ BiasSolver( int n, BiasPtr bias, Meta::TrackList context ); ~BiasSolver(); /// Returns the playlist generated after the job has finished. Meta::TrackList solution(); /// Politely asks the thread to give up and finish ASAP. - void requestAbort(); + virtual void requestAbort() override; /** * Returns true if the solver was successful, false if it was * aborted or encountered some other error. */ - virtual bool success() const; + virtual bool success() const override; /** * Choose whether the BiasSolver instance should delete itself after the query. * By passing true the instance will delete itself after emitting done, failed. * Otherwise it is the responsibility of the owner to delete the instance * when it is not needed anymore. * * Defaults to false, i.e. the BiasSolver instance will not delete itself. */ void setAutoDelete( bool autoDelete ); /** * Return the universe set (a list of the uid of every track being * considered) being used. */ static const QList& universe(); /** * Mark the universe set as out of date (i.e. it needs to be * updated). */ static void outdateUniverse(); protected: - void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) Q_DECL_OVERRIDE; - void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; + void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) override; + void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; Q_SIGNALS: /** a job must implement the following signals for the progress bar BiasedPlaylist set's us as progress sender in startSolver. */ /** Sets the total steps for the progress bar (which is always 100 for the bias solver). This signal is never emitted as the BiasedPlaylist already registers us with 100 steps. */ void totalSteps( int ); void incrementProgress(); void endProgressOperation( QObject * ); /** 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); private Q_SLOTS: void biasResultReady( const Dynamic::TrackSet &set ); void trackCollectionResultsReady( QStringList ); void trackCollectionDone(); private: /** Returns the TrackSet that would match the end of the given playlist The function blocks until the result is received */ TrackSet matchingTracks( const Meta::TrackList& playlist ) const; /** Query for the universe set (the set of all tracks in the collection being considered. This function needs to be called from a thread with an event loop. */ void getTrackCollection(); /** Try to recursive add tracks to the current solver list up to m_n tracks. * The function will return with partly filled lists. */ void addTracks( SolverList *list ); /** * Get the track referenced by the uid stored in the given * QByteArray. * @param uid The uid */ Meta::TrackPtr trackForUid( const QString& uid ) const; /** * Return a random track from the domain. */ Meta::TrackPtr getMutation(); /** * Return a random track from the given subset. * @param subset A list (representing a set) of uids stored in * QByteArrays. */ Meta::TrackPtr getRandomTrack( const TrackSet& subset ) const; /** Returns a TrackSet with all duplicates removed (except the one at "position") This helper function can be used in special cases if needed and AmarokConfig::dynamicDuplicates() == false Normally the BiasSolver will call it at for the top most bias. */ static TrackSet withoutDuplicate( int position, const Meta::TrackList& playlist, const Dynamic::TrackSet& oldSet ); /** Emits the required progress signals */ void updateProgress( const SolverList* list ); int m_n; //!< size of playlist to generate Dynamic::BiasPtr m_bias; // bias used to determine tracks. not owned by solver Meta::TrackList m_context; //!< tracks that precede the playlist Meta::TrackList m_solution; bool m_abortRequested; //!< flag set when the thread is aborted QDateTime m_startTime; mutable QMutex m_biasResultsMutex; mutable QWaitCondition m_biasResultsReady; mutable Dynamic::TrackSet m_tracks; // tracks just received from the bias. mutable QMutex m_collectionResultsMutex; mutable QWaitCondition m_collectionResultsReady; /** All uids of all the tracks in the collection */ QStringList m_collectionUids; TrackCollectionPtr m_trackCollection; bool m_allowDuplicates; int m_currentProgress; /** The maximum time we should try to spend generating the playlist */ static const int MAX_TIME_MS = 5000; }; } #endif diff --git a/src/lyrics/LyricsManager.cpp b/src/lyrics/LyricsManager.cpp new file mode 100644 index 0000000000..bd9f271777 --- /dev/null +++ b/src/lyrics/LyricsManager.cpp @@ -0,0 +1,275 @@ +/**************************************************************************************** + * Copyright (c) 2007 Leo Franchi * + * Copyright (c) 2009 Seb Ruiz * + * * + * This program is free software; you can redistribute it and/or modify it under * + * the terms of the GNU General Public License as published by the Free Software * + * Foundation; either version 2 of the License, or (at your option) any later * + * version. * + * * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY * + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * + * PARTICULAR PURPOSE. See the GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License along with * + * this program. If not, see . * + ****************************************************************************************/ + +#define DEBUG_PREFIX "LyricsManager" + +#include "LyricsManager.h" + +#include "EngineController.h" +#include "core/meta/Meta.h" +#include "core/support/Debug.h" +#include "core-impl/collections/support/CollectionManager.h" + +#include +#include +#include + +#include + + +#define APIURL "http://lyrics.wikia.com/api.php?action=query&prop=revisions&rvprop=content&format=xml&titles=" + + +LyricsManager* LyricsManager::s_self = 0; + +LyricsManager::LyricsManager() +{ + s_self = this; + connect( The::engineController(), &EngineController::trackChanged, this, &LyricsManager::newTrack ); +} + +void +LyricsManager::newTrack( Meta::TrackPtr track ) +{ + loadLyrics( track ); +} + +void +LyricsManager::lyricsResult( const QByteArray& lyricsXML, Meta::TrackPtr track ) //SLOT +{ + DEBUG_BLOCK + + QXmlStreamReader xml( lyricsXML ); + while( !xml.atEnd() ) + { + xml.readNext(); + + if( xml.name() == QStringLiteral("lyric") || xml.name() == QStringLiteral( "lyrics" ) ) + { + QString lyrics( xml.readElementText() ); + if( !isEmpty( lyrics ) ) + { + // overwrite cached lyrics (as either there were no lyircs available previously OR + // the user exlicitly agreed to overwrite the lyrics) + debug() << "setting cached lyrics..."; + track->setCachedLyrics( lyrics ); // TODO: setLyricsByPath? + emit newLyrics( track ); + } + else + { + ::error() << i18n("Retrieved lyrics is empty"); + return; + } + } + else if( xml.name() == QLatin1String("suggestions") ) + { + QVariantList suggestions; + while( xml.readNextStartElement() ) + { + if( xml.name() != QLatin1String("suggestion") ) + continue; + + const QXmlStreamAttributes &a = xml.attributes(); + + QString artist = a.value( QLatin1String("artist") ).toString(); + QString title = a.value( QLatin1String("title") ).toString(); + QString url = a.value( QLatin1String("url") ).toString(); + + if( !url.isEmpty() ) + suggestions << ( QStringList() << title << artist << url ); + + xml.skipCurrentElement(); + } + + debug() << "got" << suggestions.size() << "suggestions"; + + if( !suggestions.isEmpty() ) + emit newSuggestions( suggestions ); + + return; + } + } + + if( xml.hasError() ) + { + warning() << "errors occurred during reading lyrics xml result:" << xml.errorString(); + emit error( i18n("Lyrics data could not be parsed") ); + } +} + +void LyricsManager::loadLyrics( Meta::TrackPtr track, bool overwrite ) +{ + DEBUG_BLOCK + + if( !track ) + { + debug() << "no current track"; + return; + } + + // -- get current title and artist + QString title = track->name(); + QString artist = track->artist() ? track->artist()->name() : QString(); + + sanitizeTitle( title ); + sanitizeArtist( artist ); + + if( !isEmpty( track->cachedLyrics() ) && !overwrite ) + { + debug() << "Lyrics already cached."; + return; + } + + QUrl url( APIURL + artist + ':' + title ); + m_trackMap.insert( url, track ); + NetworkAccessManagerProxy::instance()->getData( url, this, &LyricsManager::lyricsLoaded ); +} + +void LyricsManager::lyricsLoaded( const QUrl& url, const QByteArray& data, NetworkAccessManagerProxy::Error err ) +{ + DEBUG_BLOCK + + if( err.code ) + { + warning() << "A network error occurred:" << err.description; + return; + } + + Meta::TrackPtr track = m_trackMap.take( url ); + if( !track ) + { + warning() << "No track belongs to this url:" << url.url(); + return; + } + + QDomDocument document; + document.setContent( data ); + auto list = document.elementsByTagName( QStringLiteral( "rev" ) ); + if( list.isEmpty() ) + { + if( track->album() && track->album()->albumArtist() ) + { + QString albumArtist = track->album()->albumArtist()->name(); + QString artist = track->artist() ? track->artist()->name() : QString(); + QString title = track->name(); + sanitizeTitle( title ); + sanitizeArtist( artist ); + sanitizeArtist( albumArtist ); + + //Try with album artist + if( url == QUrl( APIURL + artist + ':' + title ) && albumArtist != artist ) + { + debug() << "Try again with album artist."; + + QUrl newUrl( APIURL + albumArtist + ':' + title ); + m_trackMap.insert( newUrl, track ); + NetworkAccessManagerProxy::instance()->getData( newUrl, this, &LyricsManager::lyricsLoaded ); + return; + } + } + + debug() << "No lyrics found for track:" << track->name(); + return; + } + + QString rev = list.at( 0 ).toElement().text(); + if( rev.contains( QStringLiteral( "lyrics" ) ) ) + { + int lindex = rev.indexOf( QStringLiteral( "" ) ); + int rindex = rev.indexOf( QStringLiteral( "" ) ); + lyricsResult( (rev.mid( lindex, rindex - lindex ) + "" ).toUtf8(), track ); + } + else if( rev.contains( QStringLiteral( "lyric" ) ) ) + { + int lindex = rev.indexOf( QStringLiteral( "" ) ); + int rindex = rev.indexOf( QStringLiteral( "" ) ); + lyricsResult( (rev.mid( lindex, rindex - lindex ) + "" ).toUtf8(), track ); + } + else if( rev.contains( QStringLiteral( "#REDIRECT" ) ) ) + { + debug() << "Redirect:" << data; + + int lindex = rev.indexOf( QStringLiteral( "#REDIRECT [[" ) ) + 12; + int rindex = rev.indexOf( QStringLiteral( "]]" ) ); + QStringList list = rev.mid( lindex, rindex - lindex ).split( ':' ); + if( list.size() == 2 ) + { + list[0] = list[0].replace( '&', QStringLiteral( "%26" ) ); + list[1] = list[1].replace( '&', QStringLiteral( "%26" ) ); + QUrl newUrl( APIURL + list.join( ':' ) ); + m_trackMap.insert( newUrl, track ); + NetworkAccessManagerProxy::instance()->getData( newUrl, this, &LyricsManager::lyricsLoaded ); + } + } + else + warning() << "No lyrics found in data:" << data; +} + +void LyricsManager::sanitizeTitle( QString& title ) +{ + const QString magnatunePreviewString = QLatin1String( "PREVIEW: buy it at www.magnatune.com" ); + + if( title.contains(magnatunePreviewString, Qt::CaseSensitive) ) + title = title.remove( " (" + magnatunePreviewString + ')' ); + + title = title.remove( QStringLiteral( "(Live)" ) ); + title = title.remove( QStringLiteral( "(live)" ) ); + title = title.replace( '`', QStringLiteral( "'" ) ); + title = title.replace( '&', QStringLiteral( "%26" ) ); +} + +void LyricsManager::sanitizeArtist( QString& artist ) +{ + const QString magnatunePreviewString = QLatin1String( "PREVIEW: buy it at www.magnatune.com" ); + + if( artist.contains(magnatunePreviewString, Qt::CaseSensitive) ) + artist = artist.remove( " (" + magnatunePreviewString + ')' ); + + // strip "featuring " from the artist + int strip = artist.toLower().indexOf( " ft. "); + if ( strip != -1 ) + artist = artist.mid( 0, strip ); + + strip = artist.toLower().indexOf( " feat. " ); + if ( strip != -1 ) + artist = artist.mid( 0, strip ); + + strip = artist.toLower().indexOf( " featuring " ); + if ( strip != -1 ) + artist = artist.mid( 0, strip ); + + artist = artist.replace( '`', QStringLiteral( "'" ) ); + artist = artist.replace( '&', QStringLiteral( "%26" ) ); +} + +bool LyricsManager::isEmpty( const QString &lyrics ) const +{ + QTextEdit testItem; + + // Set the text of the TextItem. + if( Qt::mightBeRichText( lyrics ) ) + testItem.setHtml( lyrics ); + else + testItem.setPlainText( lyrics ); + + // Get the plaintext content. + // We use toPlainText() to strip all Html formatting, + // so we can test if there's any text given. + QString testText = testItem.toPlainText().trimmed(); + + return testText.isEmpty(); +} diff --git a/src/lyrics/LyricsManager.h b/src/lyrics/LyricsManager.h new file mode 100644 index 0000000000..2a83f34131 --- /dev/null +++ b/src/lyrics/LyricsManager.h @@ -0,0 +1,73 @@ +/**************************************************************************************** + * Copyright (c) 2007 Leo Franchi * + * * + * This program is free software; you can redistribute it and/or modify it under * + * the terms of the GNU General Public License as published by the Free Software * + * Foundation; either version 2 of the License, or (at your option) any later * + * version. * + * * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY * + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * + * PARTICULAR PURPOSE. See the GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License along with * + * this program. If not, see . * + ****************************************************************************************/ + +#ifndef LYRICS_MANAGER_H +#define LYRICS_MANAGER_H + +#include "amarok_export.h" +#include "core/meta/Meta.h" +#include "network/NetworkAccessManagerProxy.h" + +#include +#include +#include +#include + + +class AMAROK_EXPORT LyricsManager : public QObject +{ + Q_OBJECT + + public: + static LyricsManager* instance() + { + if( !s_self ) + s_self = new LyricsManager(); + + return s_self; + } + + /** + * Tests if the given lyrics are empty. + * + * @param lyrics The lyrics which will be tested. + * + * @return true if the given lyrics are empty, otherwise false. + */ + bool isEmpty( const QString &lyrics ) const; + + void newTrack( Meta::TrackPtr track ); + void lyricsResult( const QByteArray& lyrics, Meta::TrackPtr track ); + void lyricsLoaded( const QUrl &url, const QByteArray &data, NetworkAccessManagerProxy::Error err ); + void loadLyrics( Meta::TrackPtr track, bool overwrite = false ); + + signals: + void newLyrics( Meta::TrackPtr ); + void newSuggestions( QVariantList ); + void error( QString ); + + private: + LyricsManager(); + + void sanitizeTitle( QString &title ); + void sanitizeArtist( QString &artist ); + + static LyricsManager* s_self; + + QMap m_trackMap; +}; + +#endif diff --git a/src/musicbrainz/MusicBrainzXmlParser.h b/src/musicbrainz/MusicBrainzXmlParser.h index a4e48fe154..f6c9bf1ee9 100644 --- a/src/musicbrainz/MusicBrainzXmlParser.h +++ b/src/musicbrainz/MusicBrainzXmlParser.h @@ -1,88 +1,88 @@ /**************************************************************************************** * Copyright (c) 2010 Sergey Ivanov <123kash@gmail.com> * * Copyright (c) 2013 Alberto Villa * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General 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 MUSICBRAINZXMLPARSER_H #define MUSICBRAINZXMLPARSER_H #include #include #include #include class MusicBrainzXmlParser : public QObject, public ThreadWeaver::Job { Q_OBJECT public: enum { TrackList, ReleaseGroup }; explicit MusicBrainzXmlParser( const QString &doc ); - void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) Q_DECL_OVERRIDE; + void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) override; int type(); QMap tracks; QMap artists; QMap releases; QMap releaseGroups; 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); private: void parseElement( const QDomElement &e ); void parseChildren( const QDomElement &e ); QStringList parseRecordingList( const QDomElement &e ); QString parseRecording( const QDomElement &e ); QStringList parseReleaseList( const QDomElement &e ); QString parseRelease( const QDomElement &e ); QVariantMap parseMediumList( const QDomElement &e ); QVariantMap parseMedium( const QDomElement &e ); QVariantMap parseTrackList( const QDomElement &e ); QVariantMap parseTrack( const QDomElement &e ); QString parseReleaseGroup( const QDomElement &e ); QStringList parseArtist( const QDomElement &e ); QDomDocument m_doc; int m_type; QVariantMap m_currentTrackInfo; protected: - void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; + void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; }; #endif // MUSICBRAINZXMLPARSER_H diff --git a/src/musicbrainz/MusicDNSAudioDecoder.h b/src/musicbrainz/MusicDNSAudioDecoder.h index 9383a7040f..cf630adffe 100644 --- a/src/musicbrainz/MusicDNSAudioDecoder.h +++ b/src/musicbrainz/MusicDNSAudioDecoder.h @@ -1,90 +1,90 @@ /**************************************************************************************** * Copyright (c) 2010 Sergey Ivanov <123kash@gmail.com> * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #ifndef MUSICDNSAUDIODECODER_H #define MUSICDNSAUDIODECODER_H #include "core/meta/forward_declarations.h" #include #define DEFAULT_SAMPLE_LENGTH 135000 #define MIN_SAMPLE_LENGTH 10000 class DecodedAudioData { public: DecodedAudioData(); ~DecodedAudioData(); int sRate(); void setSampleRate( const int sampleRate ); quint8 channels(); void setChannels( const quint8 channels ); int length(); qint64 duration(); void addTime( const qint64 ms ); const char *data(); void appendData( const quint8 *data, int length ); DecodedAudioData &operator<< ( const quint8 &byte ); void flush(); private: int m_sRate; quint8 m_channels; qint64 m_duration; QByteArray *m_data; }; class MusicDNSAudioDecoder : public QObject, public ThreadWeaver::Job { Q_OBJECT public: explicit MusicDNSAudioDecoder( const Meta::TrackList &tracks, const int sampleLength = DEFAULT_SAMPLE_LENGTH ); virtual ~MusicDNSAudioDecoder(); - void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) Q_DECL_OVERRIDE; + void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) override; Q_SIGNALS: void trackDecoded( const Meta::TrackPtr, const QString ); /** 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); private: int decode( const QString &fileName, DecodedAudioData *data, const int length ); Meta::TrackList m_tracks; int m_sampleLength; protected: - void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; + void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; }; #endif // MUSICDNSAUDIODECODER_H diff --git a/src/musicbrainz/MusicDNSXmlParser.h b/src/musicbrainz/MusicDNSXmlParser.h index ea7319eae8..93f2ddf606 100644 --- a/src/musicbrainz/MusicDNSXmlParser.h +++ b/src/musicbrainz/MusicDNSXmlParser.h @@ -1,60 +1,60 @@ /**************************************************************************************** * Copyright (c) 2010 Sergey Ivanov <123kash@gmail.com> * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #ifndef MUSICDNSXMLPARSER_H #define MUSICDNSXMLPARSER_H #include #include #include class MusicDNSXmlParser : public QObject, public ThreadWeaver::Job { Q_OBJECT public: explicit MusicDNSXmlParser(QString &doc ); - void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) Q_DECL_OVERRIDE; + void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) override; QStringList puid(); private: void parseElement( const QDomElement &e ); void parseChildren( const QDomElement &e ); void parseTrack( const QDomElement &e ); void parsePUIDList( const QDomElement &e ); void parsePUID( const QDomElement &e ); QDomDocument m_doc; QStringList m_puid; 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); protected: - void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; + void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; }; #endif // MUSICDNSXMLPARSER_H diff --git a/src/playlist/PlaylistController.cpp b/src/playlist/PlaylistController.cpp index d6b8442b48..df22581b3f 100644 --- a/src/playlist/PlaylistController.cpp +++ b/src/playlist/PlaylistController.cpp @@ -1,650 +1,650 @@ /**************************************************************************************** * Copyright (c) 2007-2008 Ian Monroe * * Copyright (c) 2007-2008 Nikolaj Hald Nielsen * * Copyright (c) 2008 Seb Ruiz * * Copyright (c) 2008 Soren Harward * * Copyright (c) 2009 John Atkinson * * Copyright (c) 2009,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) 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 "Playlist::Controller" // WORKAROUND for QTBUG-25960. Required for Qt versions < 4.8.5 in combination with libc++. #define QT_NO_STL 1 #include #undef QT_NO_STL #include "PlaylistController.h" #include "EngineController.h" #include "amarokconfig.h" #include "core/collections/QueryMaker.h" #include "core/support/Debug.h" #include "core-impl/meta/cue/CueFileSupport.h" #include "core-impl/meta/file/File.h" #include "core-impl/meta/multi/MultiTrack.h" #include "core-impl/playlists/types/file/PlaylistFileSupport.h" #include "core-impl/support/TrackLoader.h" #include "playlist/PlaylistActions.h" #include "playlist/PlaylistModelStack.h" #include "playlistmanager/PlaylistManager.h" #include #include #include using namespace Playlist; namespace The { AMAROK_EXPORT Controller* playlistController() { return Controller::instance(); } } Controller* Controller::s_instance = 0; Controller* Controller::instance() { if( s_instance == 0 ) s_instance = new Controller(); return s_instance; } void Controller::destroy() { if( s_instance ) { delete s_instance; s_instance = 0; } } Controller::Controller() : QObject() , m_undoStack( new QUndoStack( this ) ) { DEBUG_BLOCK //As a rule, when talking to the playlist one should always use the topmost model, as //Playlist::ModelStack::instance->top() or simply The::playlist(). //This is an exception, because we handle the presence of tracks in the bottom model, //so we get a pointer to the bottom model and use it with great care. // TODO: get these values only when we really need them to loosen up the // coupling between Controller and Model m_bottomModel = ModelStack::instance()->bottom(); m_topModel = The::playlist(); m_undoStack->setUndoLimit( 20 ); connect( m_undoStack, &QUndoStack::canRedoChanged, this, &Controller::canRedoChanged ); connect( m_undoStack, &QUndoStack::canUndoChanged, this, &Controller::canUndoChanged ); } Controller::~Controller() {} void Controller::insertOptioned( Meta::TrackPtr track, AddOptions options ) { if( !track ) return; Meta::TrackList list; list.append( track ); insertOptioned( list, options ); } void Controller::insertOptioned( Meta::TrackList list, AddOptions options ) { DEBUG_BLOCK /* Note: don't use (options & flag) here to test whether flag is present in options. * We have compound flags and for example (Queue & DirectPlay) == Queue, which * evaluates to true, which isn't usually what you want. * * Use (options & flag == flag) instead, or rather QFlag's convenience method: * options.testFlag( flag ) */ if( list.isEmpty() ) return; QString actionName = i18nc( "name of the action in undo stack", "Add tracks to playlist" ); if( options.testFlag( Queue ) ) actionName = i18nc( "name of the action in undo stack", "Queue tracks" ); if( options.testFlag( Replace ) ) actionName = i18nc( "name of the action in undo stack", "Replace playlist" ); m_undoStack->beginMacro( actionName ); if( options.testFlag( Replace ) ) { emit replacingPlaylist(); //make sure that we clear filters clear(); //make sure that we turn off dynamic mode. Amarok::actionCollection()->action( "disable_dynamic" )->trigger(); } int bottomModelRowCount = m_bottomModel->qaim()->rowCount(); int bottomModelInsertRow; if( options.testFlag( Queue ) ) { // queue is a list of playlist item ids QQueue queue = Actions::instance()->queue(); int activeRow = m_bottomModel->activeRow(); if( options.testFlag( PrependToQueue ) ) { if( activeRow >= 0 ) bottomModelInsertRow = activeRow + 1; // right after active track else if( !queue.isEmpty() ) bottomModelInsertRow = m_bottomModel->rowForId( queue.first() ); // prepend to queue else bottomModelInsertRow = bottomModelRowCount; // fallback: append to end } else // append to queue { if( !queue.isEmpty() ) bottomModelInsertRow = m_bottomModel->rowForId( queue.last() ) + 1; // after queue else if( activeRow >= 0 ) bottomModelInsertRow = activeRow + 1; // after active track else bottomModelInsertRow = bottomModelRowCount; // fallback: append to end } } else bottomModelInsertRow = bottomModelRowCount; // this guy does the thing: insertionHelper( bottomModelInsertRow, list ); if( options.testFlag( Queue ) ) { // Construct list of rows to be queued QList ids; for( int bottomModelRow = bottomModelInsertRow; bottomModelRow < bottomModelInsertRow + list.size(); bottomModelRow++ ) { ids << m_bottomModel->idAt( bottomModelRow ); } if( options.testFlag( PrependToQueue ) ) // PrependToQueue implies Queue { // append current queue to new queue and remove it foreach( const quint64 id, Actions::instance()->queue() ) { Actions::instance()->dequeue( id ); ids << id; } } Actions::instance()->queue( ids ); } m_undoStack->endMacro(); bool startPlaying = false; EngineController *engine = The::engineController(); if( options.testFlag( DirectPlay ) ) // implies PrependToQueue startPlaying = true; else if( options.testFlag( Playlist::StartPlayIfConfigured ) && AmarokConfig::startPlayingOnAdd() && engine && !engine->isPlaying() ) { // if nothing is in the queue, queue the first item we have added so that the call // to ->requestUserNextTrack() pops it. The queueing is therefore invisible to // user. Else we start playing the queue. if( Actions::instance()->queue().isEmpty() ) Actions::instance()->queue( QList() << m_bottomModel->idAt( bottomModelInsertRow ) ); startPlaying = true; } if( startPlaying ) Actions::instance()->requestUserNextTrack(); // desired track will be first in queue emit changed(); } void Controller::insertOptioned( Playlists::PlaylistPtr playlist, AddOptions options ) { insertOptioned( Playlists::PlaylistList() << playlist, options ); } void Controller::insertOptioned( Playlists::PlaylistList list, AddOptions options ) { TrackLoader::Flags flags; // if we are going to play, we need full metadata (playable tracks) if( options.testFlag( DirectPlay ) || ( options.testFlag( Playlist::StartPlayIfConfigured ) && AmarokConfig::startPlayingOnAdd() ) ) { flags |= TrackLoader::FullMetadataRequired; } if( options.testFlag( Playlist::RemotePlaylistsAreStreams ) ) flags |= TrackLoader::RemotePlaylistsAreStreams; TrackLoader *loader = new TrackLoader( flags ); // auto-deletes itself loader->setProperty( "options", QVariant::fromValue( options ) ); connect( loader, &TrackLoader::finished, this, &Controller::slotLoaderWithOptionsFinished ); loader->init( list ); } void Controller::insertOptioned( const QUrl &url, AddOptions options ) { insertOptioned( QList() << url, options ); } void Controller::insertOptioned( QList &urls, AddOptions options ) { TrackLoader::Flags flags; // if we are going to play, we need full metadata (playable tracks) if( options.testFlag( DirectPlay ) || ( options.testFlag( Playlist::StartPlayIfConfigured ) && AmarokConfig::startPlayingOnAdd() ) ) { flags |= TrackLoader::FullMetadataRequired; } if( options.testFlag( Playlist::RemotePlaylistsAreStreams ) ) flags |= TrackLoader::RemotePlaylistsAreStreams; TrackLoader *loader = new TrackLoader( flags ); // auto-deletes itself loader->setProperty( "options", QVariant::fromValue( options ) ); connect( loader, &TrackLoader::finished, this, &Controller::slotLoaderWithOptionsFinished ); loader->init( urls ); } void Controller::insertTrack( int topModelRow, Meta::TrackPtr track ) { if( !track ) return; insertTracks( topModelRow, Meta::TrackList() << track ); } void Controller::insertTracks( int topModelRow, Meta::TrackList tl ) { insertionHelper( insertionTopRowToBottom( topModelRow ), tl ); } void Controller::insertPlaylist( int topModelRow, Playlists::PlaylistPtr playlist ) { insertPlaylists( topModelRow, Playlists::PlaylistList() << playlist ); } void Controller::insertPlaylists( int topModelRow, Playlists::PlaylistList playlists ) { TrackLoader *loader = new TrackLoader(); // auto-deletes itself loader->setProperty( "topModelRow", QVariant( topModelRow ) ); connect( loader, &TrackLoader::finished, this, &Controller::slotLoaderWithRowFinished ); loader->init( playlists ); } void Controller::insertUrls( int topModelRow, QList &urls ) { TrackLoader *loader = new TrackLoader(); // auto-deletes itself loader->setProperty( "topModelRow", QVariant( topModelRow ) ); connect( loader, &TrackLoader::finished, this, &Controller::slotLoaderWithRowFinished ); loader->init( urls ); } void Controller::removeRow( int topModelRow ) { DEBUG_BLOCK removeRows( topModelRow, 1 ); } void Controller::removeRows( int topModelRow, int count ) { DEBUG_BLOCK QList rl; for( int i = 0; i < count; ++i ) rl.append( topModelRow++ ); removeRows( rl ); } void Controller::removeRows( QList& topModelRows ) { DEBUG_BLOCK RemoveCmdList bottomModelCmds; foreach( int topModelRow, topModelRows ) { if( m_topModel->rowExists( topModelRow ) ) { Meta::TrackPtr track = m_topModel->trackAt( topModelRow ); // For "undo". int bottomModelRow = m_topModel->rowToBottomModel( topModelRow ); bottomModelCmds.append( RemoveCmd( track, bottomModelRow ) ); } else warning() << "Received command to remove non-existent row. This should NEVER happen. row=" << topModelRow; } if( bottomModelCmds.size() > 0 ) m_undoStack->push( new RemoveTracksCmd( 0, bottomModelCmds ) ); emit changed(); } void Controller::removeDeadAndDuplicates() { DEBUG_BLOCK QSet uniqueTracks = m_topModel->tracks().toSet(); QList topModelRowsToRemove; foreach( Meta::TrackPtr unique, uniqueTracks ) { QList trackRows = m_topModel->allRowsForTrack( unique ).toList(); if( unique->playableUrl().isLocalFile() && !QFile::exists( unique->playableUrl().path() ) ) { // Track is Dead // TODO: Check remote files as well topModelRowsToRemove << trackRows; } else if( trackRows.size() > 1 ) { // Track is Duplicated // Remove all rows except the first for( QList::const_iterator it = ++trackRows.constBegin(); it != trackRows.constEnd(); ++it ) topModelRowsToRemove.push_back( *it ); } } if( !topModelRowsToRemove.empty() ) { m_undoStack->beginMacro( "Remove dead and duplicate entries" ); // TODO: Internationalize? removeRows( topModelRowsToRemove ); m_undoStack->endMacro(); } } void Controller::moveRow( int from, int to ) { DEBUG_BLOCK if( ModelStack::instance()->sortProxy()->isSorted() ) return; if( from == to ) return; QList source; QList target; source.append( from ); source.append( to ); // shift all the rows in between if( from < to ) { for( int i = from + 1; i <= to; i++ ) { source.append( i ); target.append( i - 1 ); } } else { for( int i = from - 1; i >= to; i-- ) { source.append( i ); target.append( i + 1 ); } } reorderRows( source, target ); } int Controller::moveRows( QList& from, int to ) { DEBUG_BLOCK if( from.size() <= 0 ) return to; qSort( from.begin(), from.end() ); if( ModelStack::instance()->sortProxy()->isSorted() ) return from.first(); to = ( to == qBound( 0, to, m_topModel->qaim()->rowCount() ) ) ? to : m_topModel->qaim()->rowCount(); from.erase( std::unique( from.begin(), from.end() ), from.end() ); int min = qMin( to, from.first() ); int max = qMax( to, from.last() ); QList source; QList target; for( int i = min; i <= max; i++ ) { if( i >= m_topModel->qaim()->rowCount() ) break; // we are likely moving below the last element, to an index that really does not exist, and thus should not be moved up. source.append( i ); target.append( i ); } int originalTo = to; foreach ( int f, from ) { if( f < originalTo ) to--; // since we are moving an item down in the list, this item will no longer count towards the target row source.removeOne( f ); } // We iterate through the items in reverse order, as this allows us to keep the target row constant // (remember that the item that was originally on the target row is pushed down) QList::const_iterator f_iter = from.constEnd(); while( f_iter != from.constBegin() ) { --f_iter; source.insert( ( to - min ), *f_iter ); } reorderRows( source, target ); return to; } void Controller::reorderRows( const QList &from, const QList &to ) { DEBUG_BLOCK if( from.size() != to.size() ) return; // validity check: each item should appear exactly once in both lists { QSet fromItems( from.toSet() ); QSet toItems( to.toSet() ); if( fromItems.size() != from.size() || toItems.size() != to.size() || fromItems != toItems ) { error() << "row move lists malformed:"; error() << from; error() << to; return; } } MoveCmdList bottomModelCmds; for( int i = 0; i < from.size(); i++ ) { debug() << "moving rows:" << from.at( i ) << "->" << to.at( i ); if( ( from.at( i ) >= 0 ) && ( from.at( i ) < m_topModel->qaim()->rowCount() ) ) if( from.at( i ) != to.at( i ) ) bottomModelCmds.append( MoveCmd( m_topModel->rowToBottomModel( from.at( i ) ), m_topModel->rowToBottomModel( to.at( i ) ) ) ); } if( bottomModelCmds.size() > 0 ) m_undoStack->push( new MoveTracksCmd( 0, bottomModelCmds ) ); emit changed(); } void Controller::undo() { DEBUG_BLOCK m_undoStack->undo(); emit changed(); } void Controller::redo() { DEBUG_BLOCK m_undoStack->redo(); emit changed(); } void Controller::clear() { DEBUG_BLOCK removeRows( 0, ModelStack::instance()->bottom()->qaim()->rowCount() ); emit changed(); } /************************************************** * Private Functions **************************************************/ void Controller::slotLoaderWithOptionsFinished( const Meta::TrackList &tracks ) { QObject *loader = sender(); if( !loader ) { error() << __PRETTY_FUNCTION__ << "must be connected to TrackLoader"; return; } QVariant options = loader->property( "options" ); if( !options.canConvert() ) { error() << __PRETTY_FUNCTION__ << "loader property 'options' is not valid"; return; } if( !tracks.isEmpty() ) insertOptioned( tracks, options.value() ); } void Controller::slotLoaderWithRowFinished( const Meta::TrackList &tracks ) { QObject *loader = sender(); if( !loader ) { error() << __PRETTY_FUNCTION__ << "must be connected to TrackLoader"; return; } QVariant topModelRow = loader->property( "topModelRow" ); if( !topModelRow.isValid() || topModelRow.type() != QVariant::Int ) { error() << __PRETTY_FUNCTION__ << "loader property 'topModelRow' is not a valid integer"; return; } if( !tracks.isEmpty() ) insertTracks( topModelRow.toInt(), tracks ); } int Controller::insertionTopRowToBottom( int topModelRow ) { if( ( topModelRow < 0 ) || ( topModelRow > m_topModel->qaim()->rowCount() ) ) { error() << "Row number invalid, using bottom:" << topModelRow; topModelRow = m_topModel->qaim()->rowCount(); // Failsafe: append. } if( ModelStack::instance()->sortProxy()->isSorted() ) // if the playlist is sorted there's no point in placing the added tracks at any // specific point in relation to another track, so we just append them. return m_bottomModel->qaim()->rowCount(); else return m_topModel->rowToBottomModel( topModelRow ); } void Controller::insertionHelper( int bottomModelRow, Meta::TrackList& tl ) { //expand any tracks that are actually playlists into multisource tracks //and any tracks with an associated cue file Meta::TrackList modifiedList; QMutableListIterator i( tl ); while( i.hasNext() ) { i.next(); Meta::TrackPtr track = i.value(); if( !track ) { /*ignore*/ } - else if( typeid( *track.data() ) == typeid( MetaFile::Track ) ) + else if( MetaFile::TrackPtr::dynamicCast( track ) ) { QUrl cuesheet = MetaCue::CueFileSupport::locateCueSheet( track->playableUrl() ); if( !cuesheet.isEmpty() ) { MetaCue::CueFileItemMap cueMap = MetaCue::CueFileSupport::loadCueFile( cuesheet, track ); if( !cueMap.isEmpty() ) { Meta::TrackList cueTracks = MetaCue::CueFileSupport::generateTimeCodeTracks( track, cueMap ); if( !cueTracks.isEmpty() ) - modifiedList << cueTracks; + modifiedList << cueTracks; else modifiedList << track; } else modifiedList << track; } else modifiedList << track; } else { modifiedList << track; } } InsertCmdList bottomModelCmds; foreach( Meta::TrackPtr t, modifiedList ) bottomModelCmds.append( InsertCmd( t, bottomModelRow++ ) ); if( bottomModelCmds.size() > 0 ) m_undoStack->push( new InsertTracksCmd( 0, bottomModelCmds ) ); emit changed(); } diff --git a/src/playlistgenerator/ConstraintSolver.h b/src/playlistgenerator/ConstraintSolver.h index 5d81475095..f4bfd5f1b0 100644 --- a/src/playlistgenerator/ConstraintSolver.h +++ b/src/playlistgenerator/ConstraintSolver.h @@ -1,126 +1,124 @@ /**************************************************************************************** * Copyright (c) 2008-2012 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 . * ****************************************************************************************/ #ifndef APG_CONSTRAINTSOLVER #define APG_CONSTRAINTSOLVER #include "core/meta/forward_declarations.h" #include #include #include #include class ConstraintNode; namespace Collections { class QueryMaker; } namespace APG { class ConstraintSolver : public QObject, public ThreadWeaver::Job { Q_OBJECT public: typedef QHash Population; static const int QUALITY_RANGE; // allows used to adjust speed/quality tradeoff ConstraintSolver( ConstraintNode*, int ); ~ConstraintSolver(); Meta::TrackList getSolution() const; bool satisfied() const; int serial() const { return m_serialNumber; } int iterationCount() const { return m_maxGenerations; } // overloaded ThreadWeaver::Job functions bool canBeExecuted(); - bool success() const; - - public Q_SLOTS: - void requestAbort(); + virtual bool success() const override; + virtual void requestAbort() override; Q_SIGNALS: void readyToRun(); void incrementProgress(); void totalSteps( int ); void endProgressOperation( QObject* ); /** 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); protected: - void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) Q_DECL_OVERRIDE; // from ThreadWeaver::Job + void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) override; // from ThreadWeaver::Job private Q_SLOTS: void receiveQueryMakerData( Meta::TrackList ); void receiveQueryMakerDone(); private: int m_serialNumber; // a randomly-generated serial number to help with debugging double m_satisfactionThreshold; double m_finalSatisfaction; ConstraintNode* const m_constraintTreeRoot; Collections::QueryMaker* m_qm; mutable Meta::TrackList m_domain; // tracks available to solver QMutex m_domainMutex; bool m_domainReductionFailed; Meta::TrackList m_solvedPlaylist; // playlist generated by solver bool m_readyToRun; // job execution depends on QueryMaker finishing bool m_abortRequested; // internal mathematical functions void fill_population( Population& ); Meta::TrackList* find_best( const Population& ) const; void select_population( Population&, Meta::TrackList* ); void mutate_population( Population& ); Meta::TrackList* crossover( Meta::TrackList*, Meta::TrackList* ) const; static bool pop_comp( double, double ); Meta::TrackPtr random_track_from_domain() const; Meta::TrackList sample( Meta::TrackList, const int ) const; double rng_gaussian( const double, const double ) const; quint32 rng_poisson( const double ) const; double rng_uniform() const; quint32 playlist_size() const; bool select( const double ) const; // convenience function void dump_population( const Population& ) const; // internal mathematical parameters quint32 m_maxGenerations; quint32 m_populationSize; quint32 m_suggestedPlaylistSize; }; } // namespace APG #endif diff --git a/src/playlistmanager/PlaylistManager.cpp b/src/playlistmanager/PlaylistManager.cpp index 83eeef8c09..e21af8960e 100644 --- a/src/playlistmanager/PlaylistManager.cpp +++ b/src/playlistmanager/PlaylistManager.cpp @@ -1,532 +1,531 @@ /**************************************************************************************** * Copyright (c) 2007 Bart Cerneels * * Copyright (c) 2011 Lucas Lira Gomes * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General 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 "PlaylistManager.h" #include "amarokurls/AmarokUrl.h" #include "amarokconfig.h" #include "App.h" #include "core-impl/collections/support/CollectionManager.h" #include "core-impl/playlists/types/file/PlaylistFile.h" #include "playlist/PlaylistModelStack.h" #include "core-impl/playlists/types/file/PlaylistFileSupport.h" #include "core/podcasts/PodcastProvider.h" #include "file/PlaylistFileProvider.h" #include "file/KConfigSyncRelStore.h" #include "core-impl/podcasts/sql/SqlPodcastProvider.h" #include "playlistmanager/sql/SqlUserPlaylistProvider.h" #include "playlistmanager/SyncedPlaylist.h" #include "core/support/Debug.h" #include "core/support/Components.h" #include "core/logger/Logger.h" #include "browsers/playlistbrowser/UserPlaylistModel.h" #include #include #include #include #include #include #include #include using namespace Meta; using namespace Playlists; PlaylistManager *PlaylistManager::s_instance = 0; namespace The { PlaylistManager *playlistManager() { return PlaylistManager::instance(); } } PlaylistManager * PlaylistManager::instance() { return s_instance ? s_instance : new PlaylistManager(); } void PlaylistManager::destroy() { if (s_instance) { delete s_instance; s_instance = 0; } } PlaylistManager::PlaylistManager() { s_instance = this; m_syncRelStore = new KConfigSyncRelStore(); m_playlistFileProvider = new Playlists::PlaylistFileProvider(); addProvider( m_playlistFileProvider, UserPlaylist ); m_defaultPodcastProvider = new Podcasts::SqlPodcastProvider(); addProvider( m_defaultPodcastProvider, PlaylistManager::PodcastChannel ); CollectionManager::instance()->addTrackProvider( m_defaultPodcastProvider ); m_defaultUserPlaylistProvider = new Playlists::SqlUserPlaylistProvider(); addProvider( m_defaultUserPlaylistProvider, UserPlaylist ); } PlaylistManager::~PlaylistManager() { delete m_defaultPodcastProvider; delete m_defaultUserPlaylistProvider; delete m_playlistFileProvider; delete m_syncRelStore; } bool PlaylistManager::hasToSync( Playlists::PlaylistPtr master, Playlists::PlaylistPtr slave ) { DEBUG_BLOCK debug() << "master: " << master->uidUrl(); debug() << "slave: " << slave->uidUrl(); if( !m_syncRelStore ) return false; return m_syncRelStore->hasToSync( master, slave ); } void PlaylistManager::addProvider( Playlists::PlaylistProvider *provider, int category ) { bool newCategory = false; if( !m_providerMap.uniqueKeys().contains( category ) ) newCategory = true; //disconnect all signals connected to this object to be sure. provider->disconnect( this, 0 ); m_providerMap.insert( category, provider ); connect( provider, &Playlists::PlaylistProvider::updated, this, &PlaylistManager::slotUpdated ); connect( provider, &Playlists::PlaylistProvider::playlistAdded, this, &PlaylistManager::slotPlaylistAdded ); connect( provider, &Playlists::PlaylistProvider::playlistRemoved, this, &PlaylistManager::slotPlaylistRemoved ); if( newCategory ) emit categoryAdded( category ); emit providerAdded( provider, category ); emit updated( category ); loadPlaylists( provider, category ); } void PlaylistManager::loadPlaylists( Playlists::PlaylistProvider *provider, int category ) { foreach( Playlists::PlaylistPtr playlist, provider->playlists() ) addPlaylist( playlist, category ); } void PlaylistManager::addPlaylist( Playlists::PlaylistPtr playlist, int category ) { SyncedPlaylistPtr syncedPlaylist = m_syncRelStore->asSyncedPlaylist( playlist ); //NULL when not synced or a slave added before it's master copy ("early slave") if( syncedPlaylist ) { if( !m_syncedPlaylistMap.keys().contains( syncedPlaylist ) ) { //this can only happen when playlist == the master of the syncedPlaylist //Search for any slaves created before their master ("early slaves") //To set-up a sync between them //Only search in the category of the new playlist, i.e. no cross category syncing. foreach( Playlists::PlaylistPtr existingPlaylist, m_playlistMap.values( category ) ) { //If this is a slave asSyncedPlaylist() will make it part of the syncedPlaylist if( m_syncRelStore->asSyncedPlaylist( existingPlaylist ) == syncedPlaylist ) { m_playlistMap.remove( category, existingPlaylist ); if( !m_syncedPlaylistMap.values( syncedPlaylist ).contains( existingPlaylist ) ) m_syncedPlaylistMap.insert( syncedPlaylist, existingPlaylist ); } } } if( !m_syncedPlaylistMap.values( syncedPlaylist ).contains( playlist ) ) { m_syncedPlaylistMap.insert( syncedPlaylist, playlist ); //The synchronosation will be done in the next mainloop run m_syncNeeded.append( syncedPlaylist ); QTimer::singleShot( 0, this, &PlaylistManager::slotSyncNeeded ); } //deliberatly reusing the passed argument playlist = PlaylistPtr::dynamicCast( syncedPlaylist ); if( m_playlistMap.values( category ).contains( playlist ) ) { //no need to add it again but do let the model know something changed. emit playlistUpdated( playlist, category ); return; } } m_playlistMap.insert( category, playlist ); //reemit so models know about new playlist in their category emit playlistAdded( playlist, category ); } void PlaylistManager::removeProvider( Playlists::PlaylistProvider *provider ) { DEBUG_BLOCK if( !provider ) return; if( !m_providerMap.values( provider->category() ).contains( provider ) ) { return; } removePlaylists( provider ); m_providerMap.remove( provider->category(), provider ); emit providerRemoved( provider, provider->category() ); emit updated( provider->category() ); } void PlaylistManager::removePlaylists( Playlists::PlaylistProvider *provider ) { foreach( Playlists::PlaylistPtr playlist, m_playlistMap.values( provider->category() ) ) if( playlist->provider() && playlist->provider() == provider ) { foreach( SyncedPlaylistPtr syncedPlaylist, m_syncedPlaylistMap.keys( playlist ) ) m_syncedPlaylistMap.remove( syncedPlaylist, playlist ); removePlaylist( playlist, provider->category() ); } } void PlaylistManager::removePlaylist( Playlists::PlaylistPtr playlist, int category ) { - if( typeid( *playlist.data() ) == typeid( SyncedPlaylist ) ) + if( auto syncedPlaylist = SyncedPlaylistPtr::dynamicCast( playlist ) ) { - SyncedPlaylistPtr syncedPlaylist = SyncedPlaylistPtr::dynamicCast( playlist ); //TODO: this might be wrong if there were multiple playlists from the same provider. //remove the specific child playlist, not all from same provider. syncedPlaylist->removePlaylistsFrom( playlist->provider() ); if( syncedPlaylist->isEmpty() ) m_playlistMap.remove( category, playlist ); m_syncNeeded.removeAll( syncedPlaylist ); } else { m_playlistMap.remove( category, playlist ); } emit playlistRemoved( playlist, category ); } void PlaylistManager::slotUpdated() { Playlists::PlaylistProvider *provider = dynamic_cast( QObject::sender() ); if( !provider ) return; //forcefull reload all the providers playlists. //This is an expensive operation, the provider should use playlistAdded/Removed signals instead. removePlaylists( provider ); loadPlaylists( provider, provider->category() ); emit updated( provider->category() ); } void PlaylistManager::slotPlaylistAdded( Playlists::PlaylistPtr playlist ) { addPlaylist( playlist, playlist->provider()->category() ); } void PlaylistManager::slotPlaylistRemoved( Playlists::PlaylistPtr playlist ) { removePlaylist( playlist, playlist->provider()->category() ); } Playlists::PlaylistList PlaylistManager::playlistsOfCategory( int playlistCategory ) { return m_playlistMap.values( playlistCategory ); } PlaylistProviderList PlaylistManager::providersForCategory( int playlistCategory ) { return m_providerMap.values( playlistCategory ); } Playlists::PlaylistProvider * PlaylistManager::playlistProvider(int category, QString name) { QList providers( m_providerMap.values( category ) ); QListIterator i(providers); while( i.hasNext() ) { Playlists::PlaylistProvider * p = static_cast( i.next() ); if( p->prettyName() == name ) return p; } return 0; } bool PlaylistManager::save( Meta::TrackList tracks, const QString &name, Playlists::PlaylistProvider *toProvider, bool editName ) { //if toProvider is 0 use the default Playlists::UserPlaylistProvider (SQL) Playlists::UserPlaylistProvider *prov = toProvider ? qobject_cast( toProvider ) : m_defaultUserPlaylistProvider; if( !prov || !prov->isWritable() ) return false; Playlists::PlaylistPtr playlist = prov->save( tracks, name ); if( playlist.isNull() ) return false; if( editName ) rename( playlist ); return true; } bool PlaylistManager::import( const QUrl& fromLocation ) { // used by: PlaylistBrowserNS::UserModel::dropMimeData() AMAROK_DEPRECATED DEBUG_BLOCK if( !m_playlistFileProvider ) { debug() << "ERROR: m_playlistFileProvider was null"; return false; } return m_playlistFileProvider->import( fromLocation ); } void PlaylistManager::rename( Playlists::PlaylistPtr playlist ) { if( playlist.isNull() ) return; AmarokUrl("amarok://navigate/playlists/user playlists").run(); emit renamePlaylist( playlist ); // connected to PlaylistBrowserModel } bool PlaylistManager::rename( PlaylistPtr playlist, const QString &newName ) { Playlists::UserPlaylistProvider *provider = qobject_cast( playlist->provider() ); if( !provider || !provider->isWritable() ) return false; provider->renamePlaylist( playlist, newName ); return true; } bool PlaylistManager::deletePlaylists( Playlists::PlaylistList playlistlist ) { // Map the playlists to their respective providers QHash provLists; foreach( Playlists::PlaylistPtr playlist, playlistlist ) { // Get the providers of the respective playlists Playlists::UserPlaylistProvider *prov = qobject_cast( getProvidersForPlaylist( playlist ).first() ); if( prov ) { Playlists::PlaylistList pllist; pllist << playlist; // If the provider already has at least one playlist to delete, add another to its list if( provLists.contains( prov ) ) { provLists[ prov ] << pllist; } // If we are adding a new provider, put it in the hash, initialize its list else provLists.insert( prov, pllist ); } } // Pass each list of playlists to the respective provider for deletion bool removedSuccess = true; foreach( Playlists::UserPlaylistProvider* prov, provLists.keys() ) { removedSuccess = prov->deletePlaylists( provLists.value( prov ) ) && removedSuccess; } return removedSuccess; } QList PlaylistManager::getProvidersForPlaylist( const Playlists::PlaylistPtr playlist ) { QList providers; if( playlist.isNull() ) return providers; SyncedPlaylistPtr syncedPlaylist = SyncedPlaylistPtr::dynamicCast( playlist ); if( syncedPlaylist && m_syncedPlaylistMap.keys().contains( syncedPlaylist ) ) { foreach( Playlists::PlaylistPtr playlist, m_syncedPlaylistMap.values( syncedPlaylist ) ) if( !providers.contains( playlist->provider() ) ) providers << playlist->provider(); return providers; } Playlists::PlaylistProvider* provider = playlist->provider(); if( provider ) return providers << provider; //Iteratively check all providers' playlists for ownership QList< Playlists::PlaylistProvider* > userPlaylists = m_providerMap.values( UserPlaylist ); foreach( Playlists::PlaylistProvider* provider, userPlaylists ) { if( provider->playlists().contains( playlist ) ) return providers << provider; } return providers; } bool PlaylistManager::isWritable( const Playlists::PlaylistPtr &playlist ) { Playlists::UserPlaylistProvider *provider = qobject_cast( getProvidersForPlaylist( playlist ).first() ); if( provider ) return provider->isWritable(); else return false; } void PlaylistManager::completePodcastDownloads() { foreach( Playlists::PlaylistProvider *prov, providersForCategory( PodcastChannel ) ) { Podcasts::PodcastProvider *podcastProvider = dynamic_cast( prov ); if( !podcastProvider ) continue; podcastProvider->completePodcastDownloads(); } } void PlaylistManager::setupSync( const Playlists::PlaylistPtr master, const Playlists::PlaylistPtr slave ) { DEBUG_BLOCK debug() << "master: " << master->uidUrl(); debug() << "slave: " << slave->uidUrl(); //If there is no sync relation established between these two, then we must setup a sync. if( hasToSync( master, slave ) ) return; Playlists::PlaylistPtr tempMaster; Playlists::PlaylistPtr tempSlave; m_syncRelStore->addSync( master, slave ); foreach( const Playlists::PlaylistPtr tempPlaylist, m_playlistMap ) { if( master == tempPlaylist ) { tempMaster = tempPlaylist; break; } } foreach( const Playlists::PlaylistPtr tempPlaylist, m_playlistMap ) { if( slave == tempPlaylist ) { tempSlave = tempPlaylist; break; } } if( tempMaster && tempSlave ) { SyncedPlaylistPtr syncedPlaylist = m_syncRelStore->asSyncedPlaylist( tempMaster ); m_syncRelStore->asSyncedPlaylist( tempSlave ); Playlists::PlaylistPtr syncedPlaylistPtr = Playlists::PlaylistPtr::dynamicCast( syncedPlaylist ); int category = syncedPlaylist->master()->provider()->category(); if( !m_playlistMap.values( category ).contains( syncedPlaylistPtr ) ) { removePlaylist( tempMaster, tempMaster->provider()->category() ); removePlaylist( tempSlave, tempSlave->provider()->category() ); m_syncedPlaylistMap.insert( syncedPlaylist, tempMaster ); m_syncedPlaylistMap.insert( syncedPlaylist, tempSlave ); m_playlistMap.insert( category, syncedPlaylistPtr ); //reemit so models know about new playlist in their category emit playlistAdded( syncedPlaylistPtr, category ); } } } void PlaylistManager::slotSyncNeeded() { foreach( SyncedPlaylistPtr syncedPlaylist, m_syncNeeded ) if ( syncedPlaylist->syncNeeded() ) syncedPlaylist->doSync(); m_syncNeeded.clear(); } diff --git a/src/scanner/AbstractDirectoryWatcher.h b/src/scanner/AbstractDirectoryWatcher.h index 8b55f7b525..5746ac6234 100644 --- a/src/scanner/AbstractDirectoryWatcher.h +++ b/src/scanner/AbstractDirectoryWatcher.h @@ -1,108 +1,108 @@ /**************************************************************************************** * Copyright (c) 2010-2013 Ralf Engels * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #ifndef ABSTRACT_DIRECTORY_WATCHER_H #define ABSTRACT_DIRECTORY_WATCHER_H #include "amarok_export.h" #include "GenericScanManager.h" #include #include #include #include #include #include class QTimer; class KDirWatch; /** The AbstractDirectoryWatcher is a helper object that watches a set of directories for a collection and starts an incremental scan as soon as something changes. You need to implement the collectionFolders method. Use the Watcher like this: ThreadWeaver::Queue::instance()->enqueue( ScanDirectoryWatcherJob( this ) ); Note: When Amarok is started we wait a minute (so that the scanner does not slow down the application startup) and then we do a full incremental scan. After that we use KDirWatch to track directory changes. KDirWatch will not track changes to symbolic links! Note: The watcher needs to be a separate job because the KDirWatcher might need a long time adding recursive directories. This will prevent the directory adding from blocking the UI. */ class AMAROK_EXPORT AbstractDirectoryWatcher : public QObject, public ThreadWeaver::Job { Q_OBJECT public: AbstractDirectoryWatcher(); - void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) Q_DECL_OVERRIDE; + void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) override; void abort(); /** Pauses the emitting of the scan signal */ void setBlockScanning( bool block ); Q_SIGNALS: /** Requests the scanner to do an incremental scan. * The incremental scan will check for new files or sub-folders. * @param directory The directory to scan or and empty string if every * collection folder should be checked for changes. */ void requestScan( QList directories, GenericScanManager::ScanType type ); /** 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); protected Q_SLOTS: void delayTimeout(); void delayedScan( const QString& path ); protected: virtual QList collectionFolders() = 0; /** Adds the given directory to the list of directories for the next scan. */ void addDirToList( const QString &directory ); - void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; + void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; QTimer* m_delayedScanTimer; KDirWatch *m_watcher; /** Mutex for the wait condition */ QMutex m_mutex; QWaitCondition m_waitCondition; QMutex m_dirsMutex; QSet m_scanDirsRequested; bool m_aborted; bool m_blocked; }; #endif diff --git a/src/scanner/GenericScannerJob.h b/src/scanner/GenericScannerJob.h index 35aa34d482..3c92a0c487 100644 --- a/src/scanner/GenericScannerJob.h +++ b/src/scanner/GenericScannerJob.h @@ -1,164 +1,164 @@ /**************************************************************************************** * Copyright (c) 2003-2008 Mark Kretschmann * * Copyright (c) 2007 Maximilian Kossick * * Copyright (c) 2007 Casey Link * * Copyright (c) 2008-2009 Jeff Mitchell * * Copyright (c) 2010-2011 Ralf Engels * * Copyright (c) 2011 Bart Cerneels * * Copyright (c) 2013 Ralf Engels * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #ifndef GENERIC_SCANNERJOB_H #define GENERIC_SCANNERJOB_H #include "GenericScanManager.h" #include "collectionscanner/Directory.h" #include #include #include #include #include #include #include #include namespace CollectionScanner { class Directory; } class KProcess; class QSharedMemory; template class QSharedPointer; /** This is the job that does all the hard work with scanning. It will receive new data from the scanner process, parse it and call the ScanResultProcessor. The job will delete itself when finished or aborted. (This is important as a separate process should not be killed by another process out of order) Design Decision: The ScannerJob should parse the xml from the amarok collection scanner while it's being produced. In case the collection scanner crashes the scanner process will be restarted and the xml output from the scanner seamlessly appended. */ class GenericScannerJob : public QObject, public ThreadWeaver::Job { Q_OBJECT public: /** Creates the parse job. The constructor itself should be called from the UI thread. @param input An input io device for the scanner. The input must remain valid as long as the scanner is working (TODO: is this smart?) */ GenericScannerJob( GenericScanManager* manager, QIODevice *input, GenericScanManager::ScanType type ); GenericScannerJob( GenericScanManager* manager, QStringList scanDirsRequested, GenericScanManager::ScanType type, bool recursive = true, bool detectCharset = false ); ~GenericScannerJob(); /* ThreadWeaver::Job virtual methods */ - void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) Q_DECL_OVERRIDE; + void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) override; virtual void abort(); - void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; + void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; Q_SIGNALS: void started( GenericScanManager::ScanType type ); /** Gives the estimated count of directories that this scan will have. * This signal might not be emitted or emitted multiple times if the * count is updated. */ void directoryCount( int count ); /** * Emitted once we get the complete data for a directory. * * @param dir The directory structure with all containing tracks. It is * memory-managed using QSharedPointer - you are not allowed to convert it to a * plain pointer unless you can guarantee another QSharedPointer instance pointing * to the same object exist for the time of the existence of the plain pointer. */ void directoryScanned( QSharedPointer dir ); void succeeded(); void failed( QString message ); // and the ThreadWeaver::Job also emits done /** 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); private: /** Returns the path to the collection scanner */ QString scannerPath(); /** Tries to create the scanner process. * If unable to create the scanner a failed signal will * be emitted by this method. * @returns true if it managed to start. */ bool createScannerProcess( bool restart = false ); /** Tries to restart the scanner process. * If unable to restart the scanner a failed signal will * be emitted by this method. * @returns true if it managed to restart. */ bool restartScannerProcess(); void closeScannerProcess(); /** Parses the currently available input from m_reader. * @returns true if parsing the file is finished successfully. */ bool parseScannerOutput(); /** Wait for the scanner to produce some output or die */ void getScannerOutput(); GenericScanManager* m_manager; GenericScanManager::ScanType m_type; QStringList m_scanDirsRequested; QIODevice *m_input; int m_restartCount; bool m_abortRequested; QString m_incompleteTagBuffer; // strings received via addNewXmlData but not terminated by either a or a KProcess *m_scanner; QString m_batchfilePath; QSharedMemory *m_scannerStateMemory; // a persistent storage of the current scanner state in case it needs to be restarted. bool m_recursive; bool m_charsetDetect; QXmlStreamReader m_reader; QMutex m_mutex; // only protects m_abortRequested and the abort reason }; #endif // SCANNERJOB_H diff --git a/src/scripting/scriptengine/AmarokLyricsScript.cpp b/src/scripting/scriptengine/AmarokLyricsScript.cpp deleted file mode 100644 index aa3eda1e27..0000000000 --- a/src/scripting/scriptengine/AmarokLyricsScript.cpp +++ /dev/null @@ -1,121 +0,0 @@ -/**************************************************************************************** - * Copyright (c) 2008 Leo Franchi * - * Copyright (c) 2008 Peter ZHOU * - * * - * This program is free software; you can redistribute it and/or modify it under * - * the terms of the GNU General Public License as published by the Free Software * - * Foundation; either version 2 of the License, or (at your option) any later * - * version. * - * * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY * - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * - * PARTICULAR PURPOSE. See the GNU General 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 "AmarokLyricsScript" - -#include "AmarokLyricsScript.h" - -#include "EngineController.h" -#include "scripting/scriptmanager/ScriptManager.h" -#include "context/LyricsManager.h" -#include "core/meta/Meta.h" -#include "core/support/Amarok.h" -#include "core/support/Debug.h" - -#include -#include -#include -#include - -using namespace AmarokScript; - - -AmarokLyricsScript::AmarokLyricsScript( QScriptEngine *engine ) - : QObject( engine ) -{ - QScriptValue scriptObject = engine->newQObject( this, QScriptEngine::AutoOwnership, - QScriptEngine::ExcludeSuperClassContents ); - engine->globalObject().property( "Amarok" ).setProperty( "Lyrics", scriptObject ); - connect( ScriptManager::instance(), &ScriptManager::fetchLyrics, - this, &AmarokLyricsScript::fetchLyrics ); -} - -void -AmarokLyricsScript::showLyrics( const QString &lyrics ) const -{ - DEBUG_BLOCK - Meta::TrackPtr track = The::engineController()->currentTrack(); - if( !track ) - return; - LyricsManager::self()->lyricsResult( lyrics, false ); -} - -void -AmarokLyricsScript::showLyricsHtml( const QString &lyrics ) const -{ - DEBUG_BLOCK - Meta::TrackPtr track = The::engineController()->currentTrack(); - if( !track ) - return; - LyricsManager::self()->lyricsResultHtml( lyrics, false ); -} - -void -AmarokLyricsScript::showLyricsError( const QString &error ) const -{ - DEBUG_BLOCK - LyricsManager::self()->lyricsError( error ); -} - - -void -AmarokLyricsScript::showLyricsNotFound( const QString &msg ) const -{ - DEBUG_BLOCK - LyricsManager::self()->lyricsNotFound( msg ); -} - - -QString -AmarokLyricsScript::escape( const QString &str ) -{ - return str.toHtmlEscaped(); -} - -void -AmarokLyricsScript::setLyricsForTrack( const QString &trackUrl, const QString &lyrics ) const -{ - LyricsManager::self()->setLyricsForTrack( trackUrl, lyrics ); -} - -QString -AmarokLyricsScript::toUtf8( const QByteArray &lyrics, const QString &encoding ) -{ - QTextCodec* codec = QTextCodec::codecForName( encoding.toUtf8() ); - if( !codec ) - return QString(); - return codec->toUnicode( lyrics ); -} - -QString -AmarokLyricsScript::QStringtoUtf8( const QString &lyrics, const QString &encoding ) -{ - QTextCodec* codec = QTextCodec::codecForName( encoding.toUtf8() ); - if( !codec ) - return QString(); - return codec->toUnicode( lyrics.toLatin1() ); -} - -QByteArray -AmarokLyricsScript::fromUtf8( const QString &str, const QString &encoding ) -{ - QTextCodec* codec = QTextCodec::codecForName( encoding.toUtf8() ); - if( !codec ) - return QByteArray(); - - return codec->fromUnicode( str ); -} diff --git a/src/scripting/scriptengine/AmarokLyricsScript.h b/src/scripting/scriptengine/AmarokLyricsScript.h deleted file mode 100644 index 58dddafcd1..0000000000 --- a/src/scripting/scriptengine/AmarokLyricsScript.h +++ /dev/null @@ -1,56 +0,0 @@ -/**************************************************************************************** - * Copyright (c) 2008 Leo Franchi * - * Copyright (c) 2008 Peter ZHOU * - * * - * This program is free software; you can redistribute it and/or modify it under * - * the terms of the GNU General Public License as published by the Free Software * - * Foundation; either version 2 of the License, or (at your option) any later * - * version. * - * * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY * - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * - * PARTICULAR PURPOSE. See the GNU General 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_LYRICS_SCRIPT_H -#define AMAROK_LYRICS_SCRIPT_H - -#include "core/meta/forward_declarations.h" - -#include - -class QScriptEngine; -class QByteArray; - -namespace AmarokScript -{ - // SCRIPTDOX Amarok.Lyrics - class AmarokLyricsScript : public QObject - { - Q_OBJECT - - public: - explicit AmarokLyricsScript( QScriptEngine* scriptEngine ); - - Q_INVOKABLE void showLyrics( const QString& lyrics ) const; - - Q_INVOKABLE void showLyricsHtml( const QString& lyrics ) const; - Q_INVOKABLE void showLyricsError( const QString& error ) const; - Q_INVOKABLE void showLyricsNotFound( const QString& msg ) const; - - Q_INVOKABLE QString escape( const QString& str ); - - Q_INVOKABLE void setLyricsForTrack( const QString& trackUrl , const QString& lyrics ) const; - Q_INVOKABLE QString toUtf8( const QByteArray& lyrics, const QString& encoding = "UTF-8" ); - Q_INVOKABLE QString QStringtoUtf8( const QString& lyrics, const QString& encoding = "UTF-8" ); - Q_INVOKABLE QByteArray fromUtf8( const QString& str, const QString& encoding ); - - Q_SIGNALS: - void fetchLyrics( const QString& artist, const QString& title, const QString&, Meta::TrackPtr ); - }; -} - -#endif diff --git a/src/scripting/scriptmanager/ScriptItem.cpp b/src/scripting/scriptmanager/ScriptItem.cpp index 891e72cf74..852439f605 100644 --- a/src/scripting/scriptmanager/ScriptItem.cpp +++ b/src/scripting/scriptmanager/ScriptItem.cpp @@ -1,343 +1,338 @@ /**************************************************************************************** * Copyright (c) 2004-2010 Mark Kretschmann * * Copyright (c) 2005-2007 Seb Ruiz * * Copyright (c) 2006 Alexandre Pereira de Oliveira * * Copyright (c) 2006 Martin Ellis * * Copyright (c) 2007 Leo Franchi * * Copyright (c) 2008 Peter ZHOU * * Copyright (c) 2009 Jakob Kummerow * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General 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 "ScriptItem" #include "ScriptItem.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include "core/support/Components.h" #include "core/logger/Logger.h" #include "MainWindow.h" #include "amarokconfig.h" #include "config.h" #include "services/scriptable/ScriptableServiceManager.h" #include "scripting/scriptengine/AmarokCollectionScript.h" #include "scripting/scriptengine/AmarokScriptConfig.h" #include "scripting/scriptengine/AmarokEngineScript.h" #include "scripting/scriptengine/AmarokInfoScript.h" #include "scripting/scriptengine/AmarokKNotifyScript.h" -#include "scripting/scriptengine/AmarokLyricsScript.h" #include "scripting/scriptengine/AmarokNetworkScript.h" #include "scripting/scriptengine/AmarokOSDScript.h" #include "scripting/scriptengine/AmarokPlaylistScript.h" #include "scripting/scriptengine/AmarokScript.h" #include "scripting/scriptengine/AmarokScriptableServiceScript.h" #include "scripting/scriptengine/AmarokServicePluginManagerScript.h" #include "scripting/scriptengine/AmarokStatusbarScript.h" #include "scripting/scriptengine/AmarokStreamItemScript.h" #include "scripting/scriptengine/AmarokWindowScript.h" #include "scripting/scriptengine/AmarokScriptXml.h" #include "scripting/scriptengine/exporters/CollectionTypeExporter.h" #include "scripting/scriptengine/exporters/MetaTypeExporter.h" #include "scripting/scriptengine/exporters/QueryMakerExporter.h" #include "scripting/scriptengine/exporters/ScriptableBiasExporter.h" #include "scripting/scriptengine/ScriptImporter.h" #include "scripting/scriptengine/ScriptingDefines.h" #include "ScriptManager.h" #include #include #include #include #include #include #include //////////////////////////////////////////////////////////////////////////////// // ScriptTerminatorWidget //////////////////////////////////////////////////////////////////////////////// ScriptTerminatorWidget::ScriptTerminatorWidget( const QString &message ) : PopupWidget( 0 ) { 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 ); QLabel *alabel = new QLabel( message, this ); alabel->setWordWrap( true ); alabel->setTextFormat( Qt::RichText ); alabel->setTextInteractionFlags( Qt::TextBrowserInteraction ); alabel->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred ); alabel->setPalette( p ); QPushButton *button = new QPushButton( i18n( "Terminate" ), this ); button->setPalette(p);; connect( button, &QAbstractButton::clicked, this, &ScriptTerminatorWidget::terminate ); auto closeItem = KStandardGuiItem::close(); button = new QPushButton( closeItem.icon(), closeItem.text(), this ); button->setPalette(p); connect( button, &QAbstractButton::clicked, this, &ScriptTerminatorWidget::hide ); reposition(); } //////////////////////////////////////////////////////////////////////////////// // ScriptItem //////////////////////////////////////////////////////////////////////////////// ScriptItem::ScriptItem( QObject *parent, const QString &name, const QString &path, const KPluginInfo &info ) : QObject( parent ) , m_name( name ) , m_url( QUrl::fromLocalFile( path ) ) , m_info( info ) , m_running( false ) , m_evaluating( false ) , m_runningTime( 0 ) , m_timerId( 0 ) {} void ScriptItem::pause() { DEBUG_BLOCK if( !m_engine ) { warning() << "Script has no script engine attached:" << m_name; return; } killTimer( m_timerId ); if( m_popupWidget ) { m_popupWidget->hide(); m_popupWidget->deleteLater();; } //FIXME: Sometimes a script can be evaluating and cannot be abort? or can be reevaluating for some reason? if( m_engine->isEvaluating() ) { m_engine->abortEvaluation(); m_evaluating = false; return; } if( m_info.category() == "Scriptable Service" ) The::scriptableServiceManager()->removeRunningScript( m_name ); if( m_info.isPluginEnabled() ) { debug() << "Disabling script" << m_info.pluginName(); m_info.setPluginEnabled( false ); m_info.save(); } m_log << QString( "%1 Script ended" ).arg( QTime::currentTime().toString() ); m_running = false; m_evaluating = false; } QString ScriptItem::specPath() const { QFileInfo info( m_url.path() ); const QString specPath = QString( "%1/%2.spec" ).arg( info.path(), info.completeBaseName() ); return specPath; } void ScriptItem::timerEvent( QTimerEvent* event ) { Q_UNUSED( event ) if( m_engine && m_engine->isEvaluating() ) { m_runningTime += 100; if( m_runningTime >= 5000 ) { debug() << "5 seconds passed evaluating" << m_name; m_runningTime = 0; if( !m_popupWidget ) { m_popupWidget = new ScriptTerminatorWidget( i18n( "Script %1 has been evaluating for over" " 5 seconds now, terminate?" , m_name ) ); connect( m_popupWidget.data(), &ScriptTerminatorWidget::terminate, this, &ScriptItem::stop ); } m_popupWidget.data()->show(); } } else { if( m_popupWidget ) m_popupWidget->deleteLater(); m_runningTime = 0; } } QString ScriptItem::handleError( QScriptEngine *engine ) { QString errorString = QString( "Script Error: %1 (line: %2)" ) .arg( engine->uncaughtException().toString() ) .arg( engine->uncaughtExceptionLineNumber() ); error() << errorString; engine->clearExceptions(); stop(); return errorString; } bool ScriptItem::start( bool silent ) { DEBUG_BLOCK //load the wrapper classes m_output.clear(); initializeScriptEngine(); QFile scriptFile( m_url.path() ); scriptFile.open( QIODevice::ReadOnly ); m_running = true; m_evaluating = true; m_log << QString( "%1 Script started" ).arg( QTime::currentTime().toString() ); m_timerId = startTimer( 100 ); Q_ASSERT( m_engine ); m_output << m_engine->evaluate( scriptFile.readAll() ).toString(); debug() << "After Evaluation "<< m_name; emit evaluated( m_output.join( "\n" ) ); scriptFile.close(); if ( m_evaluating ) { m_evaluating = false; if ( m_engine->hasUncaughtException() ) { m_log << handleError( m_engine.data() ); if( !silent ) { debug() << "The Log For the script that is the borked: " << m_log; } return false; } if( m_info.category() == QLatin1String("Scriptable Service") ) m_service->slotCustomize( m_name ); } else stop(); return true; } void ScriptItem::initializeScriptEngine() { DEBUG_BLOCK if( m_engine ) return; m_engine = new AmarokScript::AmarokScriptEngine( this ); connect( m_engine.data(), &AmarokScript::AmarokScriptEngine::deprecatedCall, this, &ScriptItem::slotDeprecatedCall ); connect( m_engine.data(), &AmarokScript::AmarokScriptEngine::signalHandlerException, this, &ScriptItem::signalHandlerException ); m_engine.data()->setProcessEventsInterval( 50 ); debug() << "starting script engine:" << m_name; // first create the Amarok global script object new AmarokScript::AmarokScript( m_name, m_engine.data() ); // common utils new AmarokScript::ScriptImporter( m_engine.data(), m_url ); new AmarokScript::AmarokScriptConfig( m_name, m_engine.data() ); new AmarokScript::AmarokScriptXml( m_engine.data() ); new AmarokScript::InfoScript( m_url, m_engine.data() ); //new AmarokNetworkScript( m_engine.data() ); new AmarokScript::Downloader( m_engine.data() ); // backend new AmarokScript::AmarokCollectionScript( m_engine.data() ); new AmarokScript::AmarokEngineScript( m_engine.data() ); // UI new AmarokScript::AmarokWindowScript( m_engine.data() ); new AmarokScript::AmarokPlaylistScript( m_engine.data() ); new AmarokScript::AmarokStatusbarScript( m_engine.data() ); new AmarokScript::AmarokKNotifyScript( m_engine.data() ); new AmarokScript::AmarokOSDScript( m_engine.data() ); AmarokScript::CollectionPrototype::init( m_engine.data() ); AmarokScript::QueryMakerPrototype::init( m_engine.data() ); const QString &category = m_info.category(); - if( category.contains( QLatin1String("Lyrics") ) ) - { - new AmarokScript::AmarokLyricsScript( m_engine.data() ); - } if( category.contains( QLatin1String("Scriptable Service") ) ) { new StreamItem( m_engine.data() ); m_service = new AmarokScript::ScriptableServiceScript( m_engine.data() ); new AmarokScript::AmarokServicePluginManagerScript( m_engine.data() ); } AmarokScript::MetaTrackPrototype::init( m_engine.data() ); AmarokScript::ScriptableBiasFactory::init( m_engine.data() ); } void ScriptItem::stop() { pause(); m_engine->deleteLater(); } void ScriptItem::slotDeprecatedCall( const QString &call ) { Q_UNUSED( call ) disconnect( sender(), SIGNAL(deprecatedCall(QString)), this, 0 ); if( !AmarokConfig::enableDeprecationWarnings() ) return; QString message = i18nc( "%1 is the name of the offending script, %2 the name of the script author, and %3 the author's email" , "The script %1 uses deprecated scripting API calls. Please contact the script" " author, %2 at %3, and ask him to upgrade it before the next Amarok release." , m_info.name(), m_info.author(), m_info.email() ); Amarok::Logger::longMessage( message ); } void ScriptItem::uninstall() { emit uninstalled(); deleteLater(); } ScriptItem::~ScriptItem() { stop(); } diff --git a/src/scripting/scripts/CMakeLists.txt b/src/scripting/scripts/CMakeLists.txt index d8ec710417..20ebdc4e74 100644 --- a/src/scripting/scripts/CMakeLists.txt +++ b/src/scripting/scripts/CMakeLists.txt @@ -1,8 +1,7 @@ #add_subdirectory( radio_station_service ) #add_subdirectory( librivox_service ) -add_subdirectory( lyrics_lyricwiki ) #add_subdirectory( qtscript_debug ) #add_subdirectory( script_console ) #add_subdirectory( templates ) #add_subdirectory( webcontrol) diff --git a/src/scripting/scripts/lyrics_lyricwiki/CMakeLists.txt b/src/scripting/scripts/lyrics_lyricwiki/CMakeLists.txt deleted file mode 100644 index 30809df8a4..0000000000 --- a/src/scripting/scripts/lyrics_lyricwiki/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -install( FILES - main.js - DESTINATION ${KDE_INSTALL_DATADIR}/amarok/scripts/lyrics_lyricwiki -) - -kcoreaddons_desktop_to_json("" script.desktop) - -install( FILES ${CMAKE_CURRENT_BINARY_DIR}/script.json - DESTINATION ${KDE_INSTALL_DATADIR}/amarok/scripts/lyrics_lyricwiki -) diff --git a/src/scripting/scripts/lyrics_lyricwiki/main.js b/src/scripting/scripts/lyrics_lyricwiki/main.js deleted file mode 100644 index b0fc04beae..0000000000 --- a/src/scripting/scripts/lyrics_lyricwiki/main.js +++ /dev/null @@ -1,284 +0,0 @@ -/************************************************************************** -* Amarok 2 lyrics script to fetch lyrics from lyrics.wikia.com * -* (formerly lyricwiki.org) * -* * -* Copyright * -* (C) 2008 Aaron Reichman * -* (C) 2008 Leo Franchi * -* (C) 2008 Mark Kretschmann * -* (C) 2008 Peter ZHOU * -* (C) 2009 Jakob Kummerow * -* * -* This program is free software; you can redistribute it and/or modify * -* it under the terms of the GNU General Public License as published by * -* the Free Software Foundation; either version 2 of the License, or * -* (at your option) any later version. * -* * -* This program is distributed in the hope that it will be useful, * -* but WITHOUT ANY WARRANTY; without even the implied warranty of * -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * -* GNU 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. * -**************************************************************************/ - -/* GLOBAL VARIABLES */ -// template for the xml object that will be populated and passed to Amarok.Lyrics.showLyrics() -XML = "{lyrics}"; -// if we change variable xml it will not reinitialized on next lyrics request, so we will get lyrics from previous song -// because of that we need temp variable -NEWXML = ""; -// maximum numbers that we can follow by #REDIRECT [[Band:Song]] -MAXREDIRECTS = 3; -// url to get lyrics using mediawiki API -APIURL = "http://lyrics.wikia.com/api.php?action=query&prop=revisions&rvprop=content&format=xml&titles="; -// urlified artist and title will be here after initialization -ARTIST = ""; -TITLE = ""; -// the error message that is displayed if no lyrics were found or there was an error while trying to fetch them -ERRORMSG = "Lyrics not found. Sorry."; - -var embedLyrics = Amarok.Script.readConfig("embedLyrics", false); - -function readEmbeddedLyrics() -{ - var track = Amarok.Engine.currentTrack(); - var embeddedLyrics = track.tags["lyrics"]; - if( embeddedLyrics != undefined && embeddedLyrics !== "" ) - { - Amarok.debug( "Using embedded lyrics for track \"" + track.title + "\"" ); - Amarok.Lyrics.showLyricsHtml( embeddedLyrics ); - return true; - } - return false; -} - -function embed( lyricsXML ) -{ - if( !embedLyrics ) - return; - - Amarok.Xml.setReaderData( lyricsXML ); - var lyrics = Amarok.Xml.readFirstElementWithName( "lyric" ); - if( lyrics !== "" ) - { - Amarok.Engine.currentTrack().changeTags( { "lyrics":lyrics }, false ); - Amarok.debug( "Writing lyrics to track." ); - Amarok.debug( lyrics ); - return; - } -} - -/* receives a Wiki page (in XML format) that contains url to lyric of the requested song - this API function can correct our tags - for example we trying to receive lyrics for Nightwish:Nightwish_-_Sleepwalker (incorrect tags) - this API functions will redirect us to Nightwish:Sleepwalker (correct tags) -*/ -function onHelpReceived( response ) -{ - try - { - if( response.length == 0 ) - Amarok.Lyrics.showLyricsError( ERRORMSG ); - else - { - Amarok.Xml.setDomObjectData(response); - var urlstr = Amarok.Xml.textOfFirstDomElementWithName( "url" ); - var capture; - if(capture = /.+\/([^?=:]+:[^?=:]+)$/.exec(urlstr)) - { - // matched url is like this one: http://lyrics.wikia.com/Nightwish:Sleepwalker - // but not like this: http://lyrics.wikia.com/index.php?title=Nightwish:Sleepwalker&action=edit - var url = APIURL + capture[1]; - // this zero will not allow to execute this function again - new Downloader( url, new Function("response", "onLyricsReceived(response, 0)") ); - } - else - { - Amarok.Lyrics.showLyricsNotFound( ERRORMSG ); - } - } - } - catch( err ) - { - Amarok.Lyrics.showLyricsError( ERRORMSG ); - Amarok.debug( "script error in function onHelpReceived: " + err ); - } -} - -/* receives a Wiki page (in XML format) using wikimedia API and extracts lyrics from it */ -function onLyricsReceived( response, redirects ) -{ - try - { - if( response.length == 0 ) - Amarok.Lyrics.showLyricsError( "Unable to contact server - no website returned" ); // TODO: this should be i18n able - else - { - Amarok.Xml.setDomObjectData(response); - - var capture; - response = Amarok.Xml.textOfFirstDomElementWithName( "rev" ); - - if(capture = /<(lyrics?>)/i.exec(response)) - { // ok, lyrics found - // lyrycs can be between or tags - // such variant can be in one response: national lyrics english lyrics - // example: http://lyrics.wikia.com/api.php?action=query&prop=revisions&titles=Flёur:Колыбельная_для_Солнца&rvprop=content&format=xml - // we can not use lazy regexp because qt script don't understand it - // so let's extract lyrics with string functions - - var lindex = response.indexOf("<" + capture[1]) + capture[1].length + 1; - var rindex = response.indexOf(" upper && words[i].charAt(upper).toUpperCase() == "I" ) { - upper++; - } - } - // if the word starts with an apostrophe or parenthesis, the next character has to be uppercase - if ( words[i].charAt(0) == "'" || words[i].charAt(0) == "(" ) { - upper++; - } - // finally, perform the capitalization - if ( upper < words[i].length ) { - words[i] = words[i].substring( 0, upper ).toUpperCase() + words[i].substring( upper ); - } else { - words[i] = words[i].toUpperCase(); - } - // now take care of more special cases - // names like "McSomething" - if ( words[i].substring( 0, 2 ) == "Mc" ) { - words[i] = "Mc" + words[i][2].toUpperCase() + words[i].substring( 3 ); - } - // URI-encode the word - words[i] = encodeURIComponent( words[i] ); - } - // join the words back together and return the result - var result = words.join( "_" ); - return result; - } catch ( err ) { - Amarok.debug ( "script error in function URLify: " + err ); - } -} - -// convert all HTML entities to their applicable characters -function entityDecode(string) -{ - try - { - var convertxml = "" + string + ""; - if(Amarok.Xml.setDomObjectData(convertxml)) - { // xml is valid - return Amarok.Xml.textOfFirstDomElementWithName( "entity" ); - } - - return string; - } - catch( err ) - { - Amarok.debug( "script error in function entityDecode: " + err ); - } -} - -// entry point -function getLyrics( artist, title, url, track ) -{ - if( readEmbeddedLyrics() ) - return; - try - { - // save artist and title for later display now - NEWXML = XML.replace( "{artist}", Amarok.Lyrics.escape( artist ) ); - NEWXML = NEWXML.replace( "{title}", Amarok.Lyrics.escape( title ) ); - - // strip "featuring " from the artist - var strip = artist.toLowerCase().indexOf( " ft. "); - if ( strip != -1 ) { - artist = artist.substring( 0, strip ); - } - strip = artist.toLowerCase().indexOf( " feat. " ); - if ( strip != -1 ) { - artist = artist.substring( 0, strip ); - } - strip = artist.toLowerCase().indexOf( " featuring " ); - if ( strip != -1 ) { - artist = artist.substring( 0, strip ); - } - - // URLify artist and title - ARTIST = artist = URLify( entityDecode(artist) ); - TITLE = title = URLify( entityDecode(title) ); - - // assemble the (encoded!) URL, build a QUrl out of it and dispatch the download request - var url = APIURL + artist + ":" + title; - Amarok.debug( "request URL: " + url.toString() ); - // there was no redirections yet - new Downloader( url, new Function("response", "onLyricsReceived(response, -1)") ); - } - catch( err ) - { - Amarok.debug( "error: " + err ); - } -} - - -Amarok.Window.addSettingsSeparator(); -Amarok.Window.addSettingsMenu( "LyricWiki" ); -Amarok.Window.addCustomAction( "LyricWiki", "EmbedLyrics", "Embed Lyrics" ); -Amarok.Window.LyricWiki.EmbedLyrics.checkable = true; -Amarok.Window.LyricWiki.EmbedLyrics.checked = embedLyrics; -Amarok.Window.LyricWiki.EmbedLyrics.toggled.connect( function( checked ) { - embedLyrics = checked; - Amarok.Script.writeConfig( "embedLyrics", embedLyrics ); - } ); - -Amarok.Lyrics.fetchLyrics.connect( getLyrics ); diff --git a/src/scripting/scripts/lyrics_lyricwiki/script.desktop b/src/scripting/scripts/lyrics_lyricwiki/script.desktop deleted file mode 100644 index 3b47a61a39..0000000000 --- a/src/scripting/scripts/lyrics_lyricwiki/script.desktop +++ /dev/null @@ -1,79 +0,0 @@ -[Desktop Entry] -Icon=view-pim-journal -Type=script -ServiceTypes=KPluginInfo - -Name=Lyricwiki -Name[bs]=Lyricwiki -Name[ca]=Lyricwiki -Name[ca@valencia]=Lyricwiki -Name[cs]=Lyricwiki -Name[da]=Lyricwiki -Name[de]=Lyricwiki -Name[el]=Lyricwiki -Name[en_GB]=Lyricwiki -Name[es]=Lyricwiki -Name[fi]=Lyricwiki -Name[fr]=LyricWiki -Name[gl]=Lyricwiki -Name[hu]=Lyricwiki -Name[id]=Lyricwiki -Name[it]=Lyricwiki -Name[nl]=Lyricwiki -Name[pl]=Lyricwiki -Name[pt]=Lyricwiki -Name[pt_BR]=Lyricwiki -Name[ro]=Lyricwiki -Name[sk]=Lyricwiki -Name[sl]=Lyricwiki -Name[sr]=Лириквики -Name[sr@ijekavian]=Лириквики -Name[sr@ijekavianlatin]=LyricWiki -Name[sr@latin]=LyricWiki -Name[sv]=Lyricwiki -Name[tr]=Lyricwiki -Name[uk]=Lyricwiki -Name[x-test]=xxLyricwikixx -Name[zh_TW]=Lyricwiki -Comment=A script to fetch lyrics from lyricwiki.org -Comment[bs]=Skripta za dobavljanje tekstova pjesama sa lyricwiki.org -Comment[ca]=Un script per recuperar lletres des de lyricwiki.org -Comment[ca@valencia]=Un script per recuperar lletres des de lyricwiki.org -Comment[cs]=Skript pro stažení textů písní z lyricwiki.org -Comment[da]=Et script til at hente sangtekster fra lyricwiki.org -Comment[de]=Ein Skript, um Liedtexte von lyricwiki.org zu holen -Comment[el]=Σενάριο για μεταφορά στίχων από το lyricwiki.org -Comment[en_GB]=A script to fetch lyrics from lyricwiki.org -Comment[es]=Un guion para recuperar letras desde lyricwiki.org -Comment[fi]=Skripti, joka hakee laulun sanat lyricwiki.orgista -Comment[fr]=Un script pour télécharger des paroles depuis lyricwiki.org -Comment[gl]=Un script para obter letras de lyricwiki.org. -Comment[hu]=Egy parancsfájl dalszövegek letöltéséhez a lyricwiki.org oldalról -Comment[id]=Script untuk menarik lirik dari lyricwiki.org -Comment[it]=Uno script per scaricare i testi da lyricwiki.org -Comment[nl]=Een script om liedteksten op te halen uit lyricwiki.org -Comment[pl]=Skrypt do pobieranie słów z lyricwiki.org -Comment[pt]=Um programa para obter letras musicais a partir do lyricwiki.org -Comment[pt_BR]=Um script para baixar letras de músicas a partir do lyricwiki.org -Comment[ro]=Script ce obține versuri de la lyricwiki.org -Comment[sk]=Skript na stiahnutie textu z lyricwiki.org -Comment[sl]=Skript za pridobivanje besedil iz lyricwiki.org -Comment[sr]=Скрипта за добављање стихова са Лириквикија -Comment[sr@ijekavian]=Скрипта за добављање стихова са Лириквикија -Comment[sr@ijekavianlatin]=Skripta za dobavljanje stihova sa LyricWikija -Comment[sr@latin]=Skripta za dobavljanje stihova sa LyricWikija -Comment[sv]=Ett skript för att hämta sångtexter från lyricwiki.org -Comment[tr]=Lyricwiki.org şarkı sözleri getirmek için bir betik -Comment[uk]=Скрипт для отримання слів пісень з lyricwiki.org -Comment[x-test]=xxA script to fetch lyrics from lyricwiki.orgxx -Comment[zh_TW]=從 lyricwiki.org 抓取歌詞的文稿 - -X-KDE-PluginInfo-Author=Aaron Reichman -X-KDE-PluginInfo-Email=reldruh@gmail.com -X-KDE-PluginInfo-Name=LyricWiki -X-KDE-PluginInfo-Version=.3 -X-KDE-PluginInfo-Category=Lyrics -X-KDE-PluginInfo-Depends=Amarok2.0 -X-KDE-PluginInfo-License=LGPL -X-KDE-PluginInfo-BugAddress=submit@bugs.kde.org -X-KDE-PluginInfo-EnabledByDefault=true diff --git a/src/services/ServiceSqlQueryMaker.cpp b/src/services/ServiceSqlQueryMaker.cpp index daac71b46c..89da831284 100644 --- a/src/services/ServiceSqlQueryMaker.cpp +++ b/src/services/ServiceSqlQueryMaker.cpp @@ -1,875 +1,876 @@ /**************************************************************************************** * 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 "ServiceSqlQueryMaker" #include "ServiceSqlQueryMaker.h" #include #include "core/meta/support/MetaConstants.h" #include "core/support/Debug.h" #include "core-impl/storage/StorageManager.h" #include "ServiceSqlCollection.h" #include #include using namespace Collections; class ServiceSqlWorkerThread : public QObject, public ThreadWeaver::Job { Q_OBJECT public: ServiceSqlWorkerThread( ServiceSqlQueryMaker *queryMaker ) : QObject() , ThreadWeaver::Job() , m_queryMaker( queryMaker ) , m_aborted( false ) { //nothing to do } virtual void requestAbort() { m_aborted = true; } 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); protected: virtual void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) { Q_UNUSED(self); Q_UNUSED(thread); QString query = m_queryMaker->query(); QStringList result = m_queryMaker->runQuery( query ); if( !m_aborted ) m_queryMaker->handleResult( result ); if( m_aborted ) setStatus(Status_Aborted); else setStatus(Status_Running); } 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); } private: ServiceSqlQueryMaker *m_queryMaker; bool m_aborted; }; struct ServiceSqlQueryMaker::Private { - enum QueryType { NONE, TRACK, ARTIST, ALBUM, ALBUMARTIST, GENRE, COMPOSER, YEAR, CUSTOM }; - enum {TRACKS_TABLE = 1, ALBUMS_TABLE = 2, ARTISTS_TABLE = 4, GENRE_TABLE = 8, ALBUMARTISTS_TABLE = 16 }; + enum { TRACKS_TABLE = 1, ALBUMS_TABLE = 2, ARTISTS_TABLE = 4, GENRE_TABLE = 8, ALBUMARTISTS_TABLE = 16 }; int linkedTables; - QueryType queryType; + QueryMaker::QueryType queryType; QString query; QString queryReturnValues; QString queryFrom; QString queryMatch; QString queryFilter; QString queryOrderBy; //bool includedBuilder; //bool collectionRestriction; AlbumQueryMode albumMode; bool withoutDuplicates; int maxResultSize; QSharedPointer worker; QStack andStack; }; ServiceSqlQueryMaker::ServiceSqlQueryMaker( ServiceSqlCollection* collection, ServiceMetaFactory * metaFactory, ServiceSqlRegistry * registry ) : QueryMaker() , m_collection( collection ) , m_registry( registry ) , m_metaFactory( metaFactory ) , d( new Private ) { //d->includedBuilder = true; //d->collectionRestriction = false; - d->queryType = Private::NONE; + d->queryType = QueryMaker::None; d->linkedTables = 0; d->withoutDuplicates = false; d->maxResultSize = -1; d->andStack.push( true ); } ServiceSqlQueryMaker::~ServiceSqlQueryMaker() {} void ServiceSqlQueryMaker::abortQuery() { if( d->worker ) d->worker->requestAbort(); } void ServiceSqlQueryMaker::run() { - if( d->queryType == Private::NONE ) + if( d->queryType == QueryMaker::None ) return; //better error handling? if( d->worker && !d->worker->isFinished() ) { //the worker thread seems to be running //TODO: wait for job to complete } else { d->worker.reset( new ServiceSqlWorkerThread( this ) ); connect( d->worker.data(), &ServiceSqlWorkerThread::done, this, &ServiceSqlQueryMaker::done ); ThreadWeaver::Queue::instance()->enqueue( QSharedPointer(d->worker) ); } } void ServiceSqlQueryMaker::done( ThreadWeaver::JobPointer job ) { Q_UNUSED( job ) d->worker.clear(); emit queryDone(); } QueryMaker* ServiceSqlQueryMaker::setQueryType( QueryType type) { switch( type ) { case QueryMaker::Track: //make sure to keep this method in sync with handleTracks(QStringList) and the SqlTrack ctor - if( d->queryType == Private::NONE ) + if( d->queryType == QueryMaker::None ) { QString prefix = m_metaFactory->tablePrefix(); //d->queryFrom = ' ' + prefix + "_tracks"; d->withoutDuplicates = true; d->queryFrom = ' ' + prefix + "_tracks"; - d->queryType = Private::TRACK; + d->queryType = QueryMaker::Track; d->queryReturnValues = m_metaFactory->getTrackSqlRows() + ',' + m_metaFactory->getAlbumSqlRows() + ',' + m_metaFactory->getArtistSqlRows() + ',' + m_metaFactory->getGenreSqlRows(); d->linkedTables |= Private::GENRE_TABLE; d->linkedTables |= Private::ARTISTS_TABLE; d->linkedTables |= Private::ALBUMS_TABLE; d->queryOrderBy += " GROUP BY " + prefix + "_tracks.id"; //fixes the same track being shown several times due to being in several genres if ( d->linkedTables & Private::ARTISTS_TABLE ) { d->queryOrderBy += " ORDER BY " + prefix + "_tracks.album_id"; //make sure items are added as album groups } } return this; case QueryMaker::Artist: - if( d->queryType == Private::NONE ) + if( d->queryType == QueryMaker::None ) { QString prefix = m_metaFactory->tablePrefix(); d->queryFrom = ' ' + prefix + "_tracks"; d->linkedTables |= Private::ARTISTS_TABLE; d->linkedTables |= Private::ALBUMS_TABLE; - d->queryType = Private::ARTIST; + d->queryType = QueryMaker::Artist; d->withoutDuplicates = true; d->queryReturnValues = m_metaFactory->getArtistSqlRows(); d->queryOrderBy += " GROUP BY " + prefix + "_tracks.id"; //fixes the same track being shown several times due to being in several genres } return this; case QueryMaker::AlbumArtist: - if( d->queryType == Private::NONE ) + if( d->queryType == QueryMaker::None ) { QString prefix = m_metaFactory->tablePrefix(); d->queryFrom = ' ' + prefix + "_tracks"; d->linkedTables |= Private::ALBUMARTISTS_TABLE; - d->queryType = Private::ALBUMARTIST; + d->queryType = QueryMaker::AlbumArtist; d->withoutDuplicates = true; d->queryReturnValues = QString( "albumartists.id, " ) + "albumartists.name, " + "albumartists.description "; d->queryOrderBy += " GROUP BY " + prefix + "_tracks.id"; //fixes the same track being shown several times due to being in several genres } return this; case QueryMaker::Album: - if( d->queryType == Private::NONE ) + if( d->queryType == QueryMaker::None ) { QString prefix = m_metaFactory->tablePrefix(); d->queryFrom = ' ' + prefix + "_tracks"; - d->queryType = Private::ALBUM; + d->queryType = QueryMaker::Album; d->linkedTables |= Private::ALBUMS_TABLE; d->linkedTables |= Private::ARTISTS_TABLE; d->withoutDuplicates = true; d->queryReturnValues = m_metaFactory->getAlbumSqlRows() + ',' + m_metaFactory->getArtistSqlRows(); d->queryOrderBy += " GROUP BY " + prefix + "_tracks.id"; //fixes the same track being shown several times due to being in several genres } return this; case QueryMaker::Composer: /* if( d->queryType == Private::NONE ) { - d->queryType = Private::COMPOSER; + d->queryType = QueryMaker::Composer; d->withoutDuplicates = true; - d->linkedTables |= Private::COMPOSER_TAB; + d->linkedTables |= QueryMaker::Composer_TAB; d->queryReturnValues = "composer.name, composer.id"; }*/ return this; case QueryMaker::Genre: - if( d->queryType == Private::NONE ) + if( d->queryType == QueryMaker::None ) { QString prefix = m_metaFactory->tablePrefix(); d->queryFrom = ' ' + prefix + "_genre"; - d->queryType = Private::GENRE; - //d->linkedTables |= Private::ALBUMS_TABLE; - //d->linkedTables |= Private::GENRE_TABLE; + d->queryType = QueryMaker::Genre; + //d->linkedTables |= QueryMaker::Albums_TABLE; + //d->linkedTables |= QueryMaker::Genre_TABLE; d->withoutDuplicates = true; d->queryReturnValues = m_metaFactory->getGenreSqlRows(); d->queryOrderBy = " GROUP BY " + prefix +"_genre.name"; // HAVING COUNT ( " + prefix +"_genre.name ) > 10 "; } return this; case QueryMaker::Year: /*if( d->queryType == Private::NONE ) { d->queryType = Private::YEAR; d->withoutDuplicates = true; d->linkedTables |= Private::YEAR_TAB; d->queryReturnValues = "year.name, year.id"; }*/ return this; case QueryMaker::Custom: /* if( d->queryType == Private::NONE ) d->queryType = Private::CUSTOM;*/ return this; case QueryMaker::Label: case QueryMaker::None: return this; } return this; } QueryMaker* ServiceSqlQueryMaker::addMatch( const Meta::TrackPtr &track ) { //DEBUG_BLOCK Q_UNUSED( track ); //TODO still pondereing this one... return this; } QueryMaker* ServiceSqlQueryMaker::addMatch( const Meta::ArtistPtr &artist, QueryMaker::ArtistMatchBehaviour behaviour ) { QString prefix = m_metaFactory->tablePrefix(); if( !d ) return this; if( behaviour == AlbumArtists || behaviour == AlbumOrTrackArtists ) d->linkedTables |= Private::ALBUMARTISTS_TABLE; //this should NOT be made into a static cast as this might get called with an incompatible type! const Meta::ServiceArtist * serviceArtist = dynamic_cast( artist.data() ); d->linkedTables |= Private::ARTISTS_TABLE; if( serviceArtist ) { switch( behaviour ) { case TrackArtists: d->queryMatch += QString( " AND " + prefix + "_artists.id= '%1'" ).arg( serviceArtist->id() ); break; case AlbumArtists: d->queryMatch += QString( " AND albumartists.id= '%1'" ).arg( serviceArtist->id() ); break; case AlbumOrTrackArtists: d->queryMatch += QString( " AND ( " + prefix + "_artists.id= '%1' OR albumartists.id= '%1' )" ).arg( serviceArtist->id() ); break; } } else { switch( behaviour ) { case TrackArtists: d->queryMatch += QString( " AND " + prefix + "_artists.name= '%1'" ).arg( escape( artist->name() ) ); break; case AlbumArtists: d->queryMatch += QString( " AND albumartists.name= '%1'" ).arg( escape( artist->name() ) ); break; case AlbumOrTrackArtists: d->queryMatch += QString( " AND ( " + prefix + "_artists.name= '%1' OR albumartists.name= '%1' )" ).arg( escape( artist->name() ) ); break; } } return this; } QueryMaker* ServiceSqlQueryMaker::addMatch( const Meta::AlbumPtr &album ) { QString prefix = m_metaFactory->tablePrefix(); if( !d ) return this; //this should NOT be made into a static cast as this might get called with an incompatible type! const Meta::ServiceAlbumPtr serviceAlbum = Meta::ServiceAlbumPtr::dynamicCast( album ); d->linkedTables |= Private::ALBUMS_TABLE; d->linkedTables |= Private::ARTISTS_TABLE; - if( d->queryType == Private::GENRE ) + if( d->queryType == QueryMaker::Genre ) d->linkedTables |= Private::GENRE_TABLE; if( serviceAlbum ) { d->queryMatch += QString( " AND " + prefix + "_albums.id = '%1'" ).arg( serviceAlbum->id() ); } else { d->queryMatch += QString( " AND " + prefix + "_albums.name='%1'" ).arg( escape( album->name() ) ); } return this; } QueryMaker* ServiceSqlQueryMaker::addMatch( const Meta::GenrePtr &genre ) { QString prefix = m_metaFactory->tablePrefix(); //this should NOT be made into a static cast as this might get called with an incompatible type! const Meta::ServiceGenre* serviceGenre = static_cast( genre.data() ); if( !d || !serviceGenre ) return this; //genres link to albums in the database, so we need to start from here unless soig a track query // if ( d->queryType == Private::TRACK ) { //d->queryFrom = ' ' + prefix + "_tracks"; - d->linkedTables |= Private::ALBUMS_TABLE; + d->linkedTables |= Private::ALBUMS_TABLE; //} else //d->queryFrom = ' ' + prefix + "_albums"; //if ( d->queryType == Private::ARTIST ) - //d->linkedTables |= Private::ARTISTS_TABLE; + //d->linkedTables |= Private::ARTISTS_TABLE; d->linkedTables |= Private::GENRE_TABLE; d->queryMatch += QString( " AND " + prefix + "_genre.name = '%1'" ).arg( serviceGenre->name() ); return this; } QueryMaker* ServiceSqlQueryMaker::addMatch( const Meta::ComposerPtr &composer ) { Q_UNUSED( composer ); //TODO return this; } QueryMaker* ServiceSqlQueryMaker::addMatch( const Meta::YearPtr &year ) { Q_UNUSED( year ); //TODO return this; } QueryMaker* ServiceSqlQueryMaker::addMatch( const Meta::LabelPtr &label ) { Q_UNUSED( label ); //TODO return this; } QueryMaker* ServiceSqlQueryMaker::addFilter( qint64 value, const QString &filter, bool matchBegin, bool matchEnd ) { if( !isValidValue( value ) ) { return this; } //a few hacks needed by some of the speedup code: - if ( d->queryType == Private::GENRE ) + if ( d->queryType == QueryMaker::Genre ) { QString prefix = m_metaFactory->tablePrefix(); d->queryFrom = ' ' + prefix + "_tracks"; d->linkedTables |= Private::ALBUMS_TABLE; d->linkedTables |= Private::ARTISTS_TABLE; d->linkedTables |= Private::GENRE_TABLE; } QString like = likeCondition( filter, !matchBegin, !matchEnd ); d->queryFilter += QString( " %1 %2 %3 " ).arg( andOr(), nameForValue( value ), like ); return this; } QueryMaker* ServiceSqlQueryMaker::excludeFilter( qint64 value, const QString &filter, bool matchBegin, bool matchEnd ) { if( isValidValue( value ) ) { QString like = likeCondition( filter, !matchBegin, !matchEnd ); d->queryFilter += QString( " %1 NOT %2 %3 " ).arg( andOr(), nameForValue( value ), like ); } return this; } QueryMaker* ServiceSqlQueryMaker::addNumberFilter( qint64 value, qint64 filter, QueryMaker::NumberComparison compare ) { AMAROK_NOTIMPLEMENTED Q_UNUSED( value ) Q_UNUSED( filter ) Q_UNUSED( compare ) return this; } QueryMaker* ServiceSqlQueryMaker::excludeNumberFilter( qint64 value, qint64 filter, QueryMaker::NumberComparison compare ) { AMAROK_NOTIMPLEMENTED Q_UNUSED( value ) Q_UNUSED( filter ) Q_UNUSED( compare ) return this; } QueryMaker* ServiceSqlQueryMaker::addReturnValue( qint64 value ) { Q_UNUSED( value ); /*if( d->queryType == Private::CUSTOM ) { if ( !d->queryReturnValues.isEmpty() ) d->queryReturnValues += ','; d->queryReturnValues += nameForValue( value ); }*/ return this; } QueryMaker* ServiceSqlQueryMaker::addReturnFunction( ReturnFunction function, qint64 value ) { Q_UNUSED( value ) Q_UNUSED( function ) return this; } QueryMaker* ServiceSqlQueryMaker::orderBy( qint64 value, bool descending ) { Q_UNUSED( value ); if ( d->queryOrderBy.isEmpty() ) d->queryOrderBy = " ORDER BY name "; //TODO FIX!! d->queryOrderBy += QString( " %1 " ).arg( descending ? "DESC" : "ASC" ); return this; } QueryMaker* ServiceSqlQueryMaker::limitMaxResultSize( int size ) { d->maxResultSize = size; return this; } void ServiceSqlQueryMaker::linkTables() { if( !d->linkedTables ) return; QString prefix = m_metaFactory->tablePrefix(); //d->queryFrom = ' ' + prefix + "_tracks"; if( d->linkedTables & Private::ALBUMS_TABLE ) d->queryFrom += " LEFT JOIN " + prefix + "_albums ON " + prefix + "_tracks.album_id = " + prefix + "_albums.id"; if( d->linkedTables & Private::ARTISTS_TABLE ) d->queryFrom += " LEFT JOIN " + prefix + "_artists ON " + prefix + "_albums.artist_id = " + prefix + "_artists.id"; if( d->linkedTables & Private::ALBUMARTISTS_TABLE ) d->queryFrom += " LEFT JOIN " + prefix + "_artists AS albumartists ON " + prefix + "_albums.artist_id = albumartists.id"; if( d->linkedTables & Private::GENRE_TABLE ) d->queryFrom += " LEFT JOIN " + prefix + "_genre ON " + prefix + "_genre.album_id = " + prefix + "_albums.id"; } void ServiceSqlQueryMaker::buildQuery() { if( d->albumMode == OnlyCompilations ) return; linkTables(); QString query = "SELECT "; if ( d->withoutDuplicates ) query += "DISTINCT "; query += d->queryReturnValues; query += " FROM "; query += d->queryFrom; query += " WHERE 1 "; // oh... to not have to bother with the leadig "AND" // that may or may not be needed query += d->queryMatch; if ( !d->queryFilter.isEmpty() ) { query += " AND ( 1 "; query += d->queryFilter; query += " ) "; } //query += d->queryFilter; query += d->queryOrderBy; if ( d->maxResultSize > -1 ) query += QString( " LIMIT %1 OFFSET 0 " ).arg( d->maxResultSize ); query += ';'; d->query = query; } QString ServiceSqlQueryMaker::query() { if ( d->query.isEmpty() ) buildQuery(); return d->query; } QStringList ServiceSqlQueryMaker::runQuery( const QString &query ) { if( d->albumMode == OnlyCompilations ) return QStringList(); return m_collection->query( query ); } void ServiceSqlQueryMaker::handleResult( const QStringList &result ) { if( !result.isEmpty() ) { switch( d->queryType ) { /*case Private::CUSTOM: emit newResultReady( result ); break;*/ - case Private::TRACK: + case QueryMaker::Track: handleTracks( result ); break; - case Private::ARTIST: - case Private::ALBUMARTIST: + case QueryMaker::Artist: + case QueryMaker::AlbumArtist: handleArtists( result ); break; - case Private::ALBUM: + case QueryMaker::Album: handleAlbums( result ); break; - case Private::GENRE: + case QueryMaker::Genre: handleGenres( result ); break; - /* case Private::COMPOSER: + /* case QueryMaker::Composer: handleComposers( result ); break; case Private::YEAR: handleYears( result ); break;*/ - case Private::NONE: + case QueryMaker::None: debug() << "Warning: queryResult with queryType == NONE"; default: break; } } else { - switch( d->queryType ) { + switch( d->queryType ) + { case QueryMaker::Custom: emit newResultReady( QStringList() ); break; case QueryMaker::Track: emit newTracksReady( Meta::TrackList() ); break; case QueryMaker::Artist: emit newArtistsReady( Meta::ArtistList() ); break; case QueryMaker::Album: emit newAlbumsReady( Meta::AlbumList() ); break; case QueryMaker::AlbumArtist: emit newArtistsReady( Meta::ArtistList() ); break; case QueryMaker::Genre: emit newGenresReady( Meta::GenreList() ); break; case QueryMaker::Composer: emit newComposersReady( Meta::ComposerList() ); break; case QueryMaker::Year: emit newYearsReady( Meta::YearList() ); break; - - case QueryMaker::None: - debug() << "Warning: queryResult with queryType == NONE"; + case QueryMaker::None: + debug() << "Warning: queryResult with queryType == NONE"; + default: + break; } } //queryDone will be emitted in done(Job*) } bool ServiceSqlQueryMaker::isValidValue( qint64 value ) { return value == Meta::valTitle || value == Meta::valArtist || value == Meta::valAlbum || value == Meta::valGenre; } QString ServiceSqlQueryMaker::nameForValue( qint64 value ) { QString prefix = m_metaFactory->tablePrefix(); switch( value ) { case Meta::valTitle: d->linkedTables |= Private::TRACKS_TABLE; return prefix + "_tracks.name"; case Meta::valArtist: d->linkedTables |= Private::ARTISTS_TABLE; return prefix + "_artists.name"; case Meta::valAlbum: d->linkedTables |= Private::ALBUMS_TABLE; return prefix + "_albums.name"; case Meta::valGenre: d->queryFrom = prefix + "_tracks"; d->linkedTables |= Private::ALBUMS_TABLE; d->linkedTables |= Private::GENRE_TABLE; return prefix + "_genre.name"; default: debug() << "ERROR: unknown value in ServiceSqlQueryMaker::nameForValue( qint64 ): value=" + QString::number( value ); return QString(); } } void ServiceSqlQueryMaker::handleTracks( const QStringList &result ) { Meta::TrackList tracks; //SqlRegistry* reg = m_collection->registry(); int rowCount = ( m_metaFactory->getTrackSqlRowCount() + m_metaFactory->getAlbumSqlRowCount() + m_metaFactory->getArtistSqlRowCount() + m_metaFactory->getGenreSqlRowCount() ); int resultRows = result.count() / rowCount; for( int i = 0; i < resultRows; i++ ) { QStringList row = result.mid( i*rowCount, rowCount ); Meta::TrackPtr trackptr = m_registry->getTrack( row ); tracks.append( trackptr ); } emit newTracksReady( tracks ); } void ServiceSqlQueryMaker::handleArtists( const QStringList &result ) { Meta::ArtistList artists; // SqlRegistry* reg = m_collection->registry(); int rowCount = m_metaFactory->getArtistSqlRowCount(); int resultRows = result.size() / rowCount; for( int i = 0; i < resultRows; i++ ) { QStringList row = result.mid( i*rowCount, rowCount ); artists.append( m_registry->getArtist( row ) ); } emit newArtistsReady( artists ); } void ServiceSqlQueryMaker::handleAlbums( const QStringList &result ) { Meta::AlbumList albums; int rowCount = m_metaFactory->getAlbumSqlRowCount() + m_metaFactory->getArtistSqlRowCount(); int resultRows = result.size() / rowCount; for( int i = 0; i < resultRows; i++ ) { QStringList row = result.mid( i*rowCount, rowCount ); albums.append( m_registry->getAlbum( row ) ); } emit newAlbumsReady( albums ); } void ServiceSqlQueryMaker::handleGenres( const QStringList &result ) { Meta::GenreList genres; int rowCount = m_metaFactory->getGenreSqlRowCount(); int resultRows = result.size() / rowCount; for( int i = 0; i < resultRows; i++ ) { QStringList row = result.mid( i*rowCount, rowCount ); genres.append( m_registry->getGenre( row ) ); } emit newGenresReady( genres ); } /*void ServiceSqlQueryMaker::handleComposers( const QStringList &result ) { Meta::ComposerList composers; SqlRegistry* reg = m_collection->registry(); for( QStringListIterator iter( result ); iter.hasNext(); ) { QString name = iter.next(); QString id = iter.next(); composers.append( reg->getComposer( name, id.toInt() ) ); } emit newResultReady( composers ); } void ServiceSqlQueryMaker::handleYears( const QStringList &result ) { Meta::YearList years; SqlRegistry* reg = m_collection->registry(); for( QStringListIterator iter( result ); iter.hasNext(); ) { QString name = iter.next(); QString id = iter.next(); years.append( reg->getYear( name, id.toInt() ) ); } emit newResultReady( years ); }*/ QString ServiceSqlQueryMaker::escape( QString text ) const //krazy2:exclude=constref { auto storage = StorageManager::instance()->sqlStorage(); if( storage ) return storage->escape( text ); else return QString(); } QString ServiceSqlQueryMaker::likeCondition( const QString &text, bool anyBegin, bool anyEnd ) const { if( anyBegin || anyEnd ) { QString escaped = text; escaped = escape( escaped ); //see comments in SqlQueryMaker::likeCondition escaped.replace( '%', "/%" ).replace( '_', "/_" ); QString ret = " LIKE "; ret += '\''; if ( anyBegin ) ret += '%'; ret += escaped; if ( anyEnd ) ret += '%'; ret += '\''; //Case insensitive collation for queries ret += " COLLATE utf8_unicode_ci "; //Use / as the escape character ret += " ESCAPE '/' "; return ret; } else { return QString( " = '%1' " ).arg( escape( text ) ); } } QueryMaker* ServiceSqlQueryMaker::beginAnd() { d->queryFilter += andOr(); d->queryFilter += " ( 1 "; d->andStack.push( true ); return this; } QueryMaker* ServiceSqlQueryMaker::beginOr() { d->queryFilter += andOr(); d->queryFilter += " ( 0 "; d->andStack.push( false ); return this; } QueryMaker* ServiceSqlQueryMaker::endAndOr() { d->queryFilter += ')'; d->andStack.pop(); return this; } QString ServiceSqlQueryMaker::andOr() const { return d->andStack.top() ? " AND " : " OR "; } QueryMaker * ServiceSqlQueryMaker::setAlbumQueryMode(AlbumQueryMode mode) { d->albumMode = mode; return this; } #include "ServiceSqlQueryMaker.moc" diff --git a/src/services/ampache/AmpacheSettings.h b/src/services/ampache/AmpacheSettings.h index e49dc36a11..237b6f0cd9 100644 --- a/src/services/ampache/AmpacheSettings.h +++ b/src/services/ampache/AmpacheSettings.h @@ -1,59 +1,59 @@ /**************************************************************************************** * 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 AMPACHESETTINGS_H #define AMPACHESETTINGS_H #include "AmpacheConfig.h" #include namespace Ui { class AmpacheConfigWidget; } /** Class for handling settings for Ampache services @author */ class AmpacheSettings : public KCModule { Q_OBJECT public: explicit AmpacheSettings( QWidget *parent, const QVariantList &args ); virtual ~AmpacheSettings(); - virtual void save() Q_DECL_OVERRIDE; - virtual void load() Q_DECL_OVERRIDE; - virtual void defaults() Q_DECL_OVERRIDE; + virtual void save() override; + virtual void load() override; + virtual void defaults() override; private: AmpacheConfig m_config; Ui::AmpacheConfigWidget * m_configDialog; void loadList(); int m_lastRowEdited; int m_lastColumnEdited; private Q_SLOTS: void add(); void remove(); void serverNameChanged(const QString & text); void onCellDoubleClicked(int row, int column); void saveCellEdit(int row, int column); }; #endif diff --git a/src/services/gpodder/GpodderServiceSettings.h b/src/services/gpodder/GpodderServiceSettings.h index 005de04cc1..65eac560a1 100644 --- a/src/services/gpodder/GpodderServiceSettings.h +++ b/src/services/gpodder/GpodderServiceSettings.h @@ -1,66 +1,66 @@ /**************************************************************************************** * Copyright (c) 2007 Shane King * * Copyright (c) 2010 Stefan Derkits * * Copyright (c) 2010 Christian Wagner * * Copyright (c) 2010 Felix Winter * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #ifndef GPODDERSERVICESETTINGS_H #define GPODDERSERVICESETTINGS_H #include "GpodderServiceConfig.h" #include #include #include namespace Ui { class GpodderConfigWidget; } class GpodderServiceSettings : public KCModule { Q_OBJECT public: GpodderServiceSettings( QWidget *parent, const QVariantList &args ); virtual ~GpodderServiceSettings(); - void save() Q_DECL_OVERRIDE; - void load() Q_DECL_OVERRIDE; - void defaults() Q_DECL_OVERRIDE; + void save() override; + void load() override; + void defaults() override; private Q_SLOTS: void testLogin(); void finished(); void onError( QNetworkReply::NetworkError code ); void onParseError( ); void deviceCreationFinished(); void deviceCreationError( QNetworkReply::NetworkError code ); void settingsChanged(); private: Ui::GpodderConfigWidget *m_configDialog; GpodderServiceConfig m_config; mygpo::DeviceListPtr m_devices; mygpo::AddRemoveResultPtr m_result; bool m_enableProvider; QNetworkReply *m_createDevice; }; #endif // GPODDERSERVICESETTINGS_H diff --git a/src/services/jamendo/JamendoXmlParser.h b/src/services/jamendo/JamendoXmlParser.h index 83eb97c772..e1626dec8e 100644 --- a/src/services/jamendo/JamendoXmlParser.h +++ b/src/services/jamendo/JamendoXmlParser.h @@ -1,128 +1,128 @@ /**************************************************************************************** * 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 JAMENDOXMLPARSER_H #define JAMENDOXMLPARSER_H #include "JamendoDatabaseHandler.h" #include #include #include #include #include #include /** * Parser for the XML file from http://imgjam.com/data/dbdump_artistalbumtrack.xml.gz * * @author Nikolaj Hald Nielsen */ class JamendoXmlParser : public QObject, public ThreadWeaver::Job { Q_OBJECT public: /** * Constructor * @param fileName The file to parse */ explicit JamendoXmlParser( const QString &fileName ); /** * The function that starts the actual work. Inherited from ThreadWeaver::Job * Note the work is performed in a separate thread */ - void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) Q_DECL_OVERRIDE; + void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) override; /** * Destructor */ ~JamendoXmlParser(); /** * Reads, and starts parsing, file. Should not be used directly. * @param filename The file to read */ void readConfigFile( const QString &filename ); virtual void requestAbort (); 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); /** * Signal emitted when parsing is complete. */ void doneParsing(); private Q_SLOTS: /** * Called when the job has completed. Is executed in the GUI thread */ void completeJob(); private: JamendoDatabaseHandler * m_dbHandler; QXmlStreamReader m_reader; QString m_sFileName; QMap albumTags; //used for applying genres to individual tracks int m_nNumberOfTracks; int m_nNumberOfAlbums; int m_nNumberOfArtists; /** * Read a DOM element representing an artist */ void readArtist(); /** * Read a DOM element representing an album */ void readAlbum(); /** * Read a DOM element representing a track */ void readTrack(); void countTransaction(); int m_currentArtistId; int m_currentAlbumId; int n_numberOfTransactions; int n_maxNumberOfTransactions; QHash< int, QString > m_id3GenreHash; QMap m_albumArtistMap; bool m_aborted; protected: - void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; + void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; }; #endif diff --git a/src/services/magnatune/MagnatuneDatabaseWorker.h b/src/services/magnatune/MagnatuneDatabaseWorker.h index 5cf6840202..31b8d0310c 100644 --- a/src/services/magnatune/MagnatuneDatabaseWorker.h +++ b/src/services/magnatune/MagnatuneDatabaseWorker.h @@ -1,87 +1,87 @@ /**************************************************************************************** * 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 MAGNATUNEDATABASEWORKER_H #define MAGNATUNEDATABASEWORKER_H #include "MagnatuneMeta.h" #include "../ServiceSqlRegistry.h" #include /** A small helper class to do some simple asynchroneous database queries @author Nikolaj Hald Nielsen */ class MagnatuneDatabaseWorker : public QObject, public ThreadWeaver::Job { Q_OBJECT public: MagnatuneDatabaseWorker(); ~MagnatuneDatabaseWorker(); - void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) Q_DECL_OVERRIDE; + void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) override; void fetchMoodMap(); void fetchTrackswithMood( const QString &mood, int noOfTracks, ServiceSqlRegistry * registry ); void fetchAlbumBySku( const QString &sku, ServiceSqlRegistry * registry ); 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 gotMoodMap( QMap map ); void gotMoodyTracks( Meta::TrackList tracks ); void gotAlbumBySku( Meta::MagnatuneAlbum * album ); private Q_SLOTS: void completeJob(); private: void doFetchMoodMap(); void doFetchTrackswithMood(); void doFetchAlbumBySku(); enum taskType { FETCH_MODS, FETCH_MOODY_TRACKS, ALBUM_BY_SKU }; int m_task; QMap m_moodMap; Meta::TrackList m_moodyTracks; QString m_mood; QString m_sku; int m_noOfTracks; Meta::MagnatuneAlbum * m_album; ServiceSqlRegistry * m_registry; protected: - void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; + void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; }; #endif diff --git a/src/services/magnatune/MagnatuneInfoParser.cpp b/src/services/magnatune/MagnatuneInfoParser.cpp index d118f58355..4f7f4b0bd7 100644 --- a/src/services/magnatune/MagnatuneInfoParser.cpp +++ b/src/services/magnatune/MagnatuneInfoParser.cpp @@ -1,338 +1,338 @@ /**************************************************************************************** * Copyright (c) 2007 Nikolaj Hald Nielsen * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #include "MagnatuneInfoParser.h" #include "core/support/Components.h" #include "core/logger/Logger.h" #include "MagnatuneConfig.h" #include using namespace Meta; void MagnatuneInfoParser::getInfo(ArtistPtr artist) { showLoading( i18n( "Loading artist info..." ) ); MagnatuneArtist * magnatuneArtist = dynamic_cast( artist.data() ); if ( magnatuneArtist == 0) return; // first get the entire artist html page /* QString tempFile; QString orgHtml;*/ m_infoDownloadJob = KIO::storedGet( magnatuneArtist->magnatuneUrl(), KIO::Reload, KIO::HideProgressInfo ); Amarok::Logger::newProgressOperation( m_infoDownloadJob, i18n( "Fetching %1 Artist Info", magnatuneArtist->prettyName() ) ); connect( m_infoDownloadJob, &KJob::result, this, &MagnatuneInfoParser::artistInfoDownloadComplete ); } void MagnatuneInfoParser::getInfo(AlbumPtr album) { showLoading( i18n( "Loading album info..." ) ); MagnatuneAlbum * magnatuneAlbum = dynamic_cast( album.data() ); const QString artistName = album->albumArtist()->name(); QString infoHtml = ""; infoHtml += generateHomeLink(); infoHtml += "
"; infoHtml += artistName; infoHtml += "
"; infoHtml += magnatuneAlbum->name(); infoHtml += "

"; infoHtml += "coverUrl() + "\" align=\"middle\" border=\"1\">"; // Disable Genre line in Magnatune applet since, well, it doesn't actually put a genre there... // Nikolaj, FYI: either the thumbnails aren't working, or they aren't getting through the proxy here. That would be odd, however, as the database and // all HTML are coming through the proxy //infoHtml += "

" + i18n( "Genre: ");// + magnatuneAlbum-> infoHtml += "
" + i18n( "Release Year: %1", QString::number( magnatuneAlbum->launchYear() ) ); if ( !magnatuneAlbum->description().isEmpty() ) { //debug() << "MagnatuneInfoParser: Writing description: '" << album->getDescription() << "'"; infoHtml += "

" + i18n( "Description:" ) + "

" + magnatuneAlbum->description(); } infoHtml += "



" + i18n( "From Magnatune.com" ) + "
"; infoHtml += ""; emit ( info( infoHtml ) ); } void MagnatuneInfoParser::getInfo(TrackPtr track) { Q_UNUSED( track ); return; } void MagnatuneInfoParser::artistInfoDownloadComplete( KJob *downLoadJob ) { if ( downLoadJob->error() != 0 ) { //TODO: error handling here return ; } if ( downLoadJob != m_infoDownloadJob ) return ; //not the right job, so let's ignore it QString infoString = ( (KIO::StoredTransferJob* ) downLoadJob )->data(); //debug() << "MagnatuneInfoParser: Artist info downloaded: " << infoString; infoString = extractArtistInfo( infoString ); //debug() << "html: " << trimmedInfo; emit ( info( infoString ) ); } QString MagnatuneInfoParser::extractArtistInfo( const QString &artistPage ) { QString trimmedHtml; int sectionStart = artistPage.indexOf( "" ); int sectionEnd = artistPage.indexOf( "", sectionStart ); trimmedHtml = artistPage.mid( sectionStart, sectionEnd - sectionStart ); int buyStartIndex = trimmedHtml.indexOf( "" ); int buyEndIndex; //we are going to integrate the buying of music (I hope) so remove these links while ( buyStartIndex != -1 ) { buyEndIndex = trimmedHtml.indexOf( "", buyStartIndex ) + 18; trimmedHtml.remove( buyStartIndex, buyEndIndex - buyStartIndex ); buyStartIndex = trimmedHtml.indexOf( "", buyStartIndex ); } QString infoHtml = ""; infoHtml += generateHomeLink(); infoHtml += trimmedHtml; infoHtml += ""; return infoHtml; } void MagnatuneInfoParser::getFrontPage() { if( !m_cachedFrontpage.isEmpty() ) { emit ( info( m_cachedFrontpage ) ); return; } showLoading( i18n( "Loading Magnatune.com frontpage..." ) ); m_pageDownloadJob = KIO::storedGet( QUrl("http://magnatune.com/amarok_frontpage.html"), KIO::Reload, KIO::HideProgressInfo ); Amarok::Logger::newProgressOperation( m_pageDownloadJob, i18n( "Fetching Magnatune.com front page" ) ); connect( m_pageDownloadJob, &KJob::result, this, &MagnatuneInfoParser::frontpageDownloadComplete ); } void MagnatuneInfoParser::getFavoritesPage() { MagnatuneConfig config; if ( !config.isMember() ) return; showLoading( i18n( "Loading your Magnatune.com favorites page..." ) ); QString type; if( config.membershipType() == MagnatuneConfig::STREAM ) type = "stream"; else type = "download"; QString user = config.username(); QString password = config.password(); QUrl url = QUrl::fromUserInput( "http://" + user + ":" + password + "@" + type.toLower() + ".magnatune.com/member/amarok_favorites.php" ); m_pageDownloadJob = KIO::storedGet( url, KIO::Reload, KIO::HideProgressInfo ); Amarok::Logger::newProgressOperation( m_pageDownloadJob, i18n( "Loading your Magnatune.com favorites page..." ) ); connect( m_pageDownloadJob, &KJob::result, this, &MagnatuneInfoParser::userPageDownloadComplete ); } void MagnatuneInfoParser::getRecommendationsPage() { MagnatuneConfig config; if ( !config.isMember() ) return; showLoading( i18n( "Loading your personal Magnatune.com recommendations page..." ) ); QString type; if( config.membershipType() == MagnatuneConfig::STREAM ) type = "stream"; else type = "download"; QString user = config.username(); QString password = config.password(); QUrl url = QUrl::fromUserInput( "http://" + user + ":" + password + "@" + type.toLower() + ".magnatune.com/member/amarok_recommendations.php" ); m_pageDownloadJob = KIO::storedGet( url, KIO::Reload, KIO::HideProgressInfo ); Amarok::Logger::newProgressOperation( m_pageDownloadJob, i18n( "Loading your personal Magnatune.com recommendations page..." ) ); connect( m_pageDownloadJob, &KJob::result, this, &MagnatuneInfoParser::userPageDownloadComplete ); } void MagnatuneInfoParser::frontpageDownloadComplete( KJob * downLoadJob ) { if ( downLoadJob->error() != 0 ) { //TODO: error handling here return ; } if ( downLoadJob != m_pageDownloadJob ) return ; //not the right job, so let's ignore it QString infoString = ((KIO::StoredTransferJob* )downLoadJob)->data(); //insert menu MagnatuneConfig config; if( config.isMember() ) infoString.replace( "", generateMemberMenu() ); //insert fancy amarok url links to the artists infoString = createArtistLinks( infoString ); if( m_cachedFrontpage.isEmpty() ) m_cachedFrontpage = infoString; emit ( info( infoString ) ); } void MagnatuneInfoParser::userPageDownloadComplete( KJob * downLoadJob ) { - if ( !downLoadJob->error() == 0 ) + if ( downLoadJob->error() ) { //TODO: error handling here return ; } if ( downLoadJob != m_pageDownloadJob ) return ; //not the right job, so let's ignore it QString infoString = ((KIO::StoredTransferJob* )downLoadJob)->data(); //insert menu MagnatuneConfig config; if( config.isMember() ) infoString.replace( "", generateMemberMenu() ); //make sure that any pages that use the old command name "service_magnatune" replaces it with "service-magnatune" infoString.replace( "service_magnatune", "service-magnatune" ); emit ( info( infoString ) ); } QString MagnatuneInfoParser::generateMemberMenu() { QString homeUrl = "amarok://service-magnatune?command=show_home"; QString favoritesUrl = "amarok://service-magnatune?command=show_favorites"; QString recommendationsUrl = "amarok://service-magnatune?command=show_recommendations"; QString menu = "
" "[Home] " "[Favorites] " "[Recommendations] " "
"; return menu; } QString MagnatuneInfoParser::generateHomeLink() { QString homeUrl = "amarok://service-magnatune?command=show_home"; QString link = "
" "[Home] " "
"; return link; } QString MagnatuneInfoParser::createArtistLinks( const QString &page ) { //the artist name is wrapped in artist QString returnPage = page; int startTokenLength = QString( "" ).length(); int offset = 0; int startTokenIndex = page.indexOf( "", offset ); int endTokenIndex = 0; while( startTokenIndex != -1 ) { endTokenIndex = page.indexOf( "", startTokenIndex ); if( endTokenIndex == -1 ) break; //bail out offset = endTokenIndex; //get the artist namespace int artistLength = endTokenIndex - ( startTokenIndex + startTokenLength ); QString artist = page.mid( startTokenIndex + startTokenLength, artistLength ); //replace in the artist amarok url QString replaceString = "" + artist + ""; QString artistLink = "" + artist + ""; returnPage = returnPage.replace( replaceString, artistLink ); startTokenIndex = page.indexOf( "", offset ); } return returnPage; } diff --git a/src/services/magnatune/MagnatuneStore.cpp b/src/services/magnatune/MagnatuneStore.cpp index f4242c8542..325be10f3a 100644 --- a/src/services/magnatune/MagnatuneStore.cpp +++ b/src/services/magnatune/MagnatuneStore.cpp @@ -1,746 +1,745 @@ /**************************************************************************************** * 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/logger/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::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::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 ) ) { - + if ( auto track = AmarokSharedPointer::dynamicCast( dataPtr ) ) + { 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() ); + } + else if ( auto album = AmarokSharedPointer::dynamicCast( dataPtr ) ) + { + m_currentAlbum = album.data(); debug() << "is right type (album) named " << m_currentAlbum->name(); m_downloadAlbumButton->setEnabled( true ); - } else { - + } + 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::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/services/magnatune/MagnatuneXmlParser.h b/src/services/magnatune/MagnatuneXmlParser.h index c351a8e512..1f90d473bf 100644 --- a/src/services/magnatune/MagnatuneXmlParser.h +++ b/src/services/magnatune/MagnatuneXmlParser.h @@ -1,146 +1,146 @@ /**************************************************************************************** * 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 . * ****************************************************************************************/ #ifndef MAGNATUNEXMLPARSER_H #define MAGNATUNEXMLPARSER_H #include "MagnatuneDatabaseHandler.h" #include "MagnatuneMeta.h" #include #include #include #include #include #include /** * Parser for the XML file from http://magnatune.com/info/album_info.xml * * @author Nikolaj Hald Nielsen */ class MagnatuneXmlParser : public QObject, public ThreadWeaver::Job { Q_OBJECT public: /** * Constructor * @param fileName The file to parse * @return Pointer to new object */ explicit MagnatuneXmlParser( const QString &fileName ); /** * The function that starts the actual work. Inherited from ThreadWeaver::Job * Note the work is performed in a separate thread */ - void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) Q_DECL_OVERRIDE; + void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) override; /** * Destructor * @return none */ ~MagnatuneXmlParser(); /** * Reads, and starts parsing, file. Should not be used directly. * @param filename The file to read */ void readConfigFile( const QString &filename ); void setDbHandler( MagnatuneDatabaseHandler * dbHandler ); 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); /** * Signal emmited when parsing is complete. */ void doneParsing(); private Q_SLOTS: /** * Called when the job has completed. Is executed in the GUI thread */ void completeJob(); private: QMap artistNameIdMap; QString m_currentArtist; QString m_currentArtistGenre; /** * Parses a DOM element * @param e The element to parse */ void parseElement( const QDomElement &e ); /** * Parses all children of a DOM element * @param e The element whose children is to be parsed */ void parseChildren( const QDomElement &e ); /** * Parse a DOM element representing an album * @param e The album element to parse */ void parseAlbum( const QDomElement &e ); /** * Parse a DOM element representing a track * @param e The track element to parse */ void parseTrack( const QDomElement &e ); /** * Parse the moods of a track * @param e The moods element to parse */ void parseMoods( const QDomElement &e ); QScopedPointer m_pCurrentAlbum; QScopedPointer m_pCurrentArtist; QList m_currentAlbumTracksList; QStringList m_currentTrackMoodList; QString m_sFileName; int m_nNumberOfTracks; int m_nNumberOfAlbums; int m_nNumberOfArtists; MagnatuneDatabaseHandler * m_dbHandler; protected: - void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; + void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; }; #endif diff --git a/src/services/mp3tunes/Mp3tunesWorkers.h b/src/services/mp3tunes/Mp3tunesWorkers.h index 40970d96e1..8b409c0503 100644 --- a/src/services/mp3tunes/Mp3tunesWorkers.h +++ b/src/services/mp3tunes/Mp3tunesWorkers.h @@ -1,313 +1,313 @@ /**************************************************************************************** * Copyright (c) 2007,2008 Casey Link * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #ifndef MP3TUNESWORKERS_H #define MP3TUNESWORKERS_H #include "Mp3tunesLocker.h" #include #include #include /** * Allows for threading the logging in process. */ class Mp3tunesLoginWorker : public QObject, public ThreadWeaver::Job { Q_OBJECT public: Mp3tunesLoginWorker( Mp3tunesLocker* locker, const QString &username, const QString &password ); ~Mp3tunesLoginWorker(); - void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) Q_DECL_OVERRIDE; + void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) override; 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 finishedLogin( const QString &sessionId ); private Q_SLOTS: void completeJob(); private: Mp3tunesLocker* m_locker; QString m_sessionId; QString m_username; QString m_password; protected: - void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; + void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; }; /** * Allows for threading the artist fetching process */ class Mp3tunesArtistFetcher : public QObject, public ThreadWeaver::Job { Q_OBJECT public: explicit Mp3tunesArtistFetcher( Mp3tunesLocker * locker ); ~Mp3tunesArtistFetcher(); - void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) Q_DECL_OVERRIDE; + void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) override; 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 artistsFetched( QList ); private Q_SLOTS: void completeJob(); private: Mp3tunesLocker* m_locker; QList m_artists; protected: - void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; + void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; }; /** * Allows for threading the albumWithArtistId fetching process */ class Mp3tunesAlbumWithArtistIdFetcher : public QObject, public ThreadWeaver::Job { Q_OBJECT public: Mp3tunesAlbumWithArtistIdFetcher( Mp3tunesLocker * locker, int artistId ); ~Mp3tunesAlbumWithArtistIdFetcher(); - void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) Q_DECL_OVERRIDE; + void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) override; 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 albumsFetched( QList ); private Q_SLOTS: void completeJob(); private: int m_artistId; Mp3tunesLocker* m_locker; QList m_albums; protected: - void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; + void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; }; /** * Allows for threading the trackWithAlbumId fetching process */ class Mp3tunesTrackWithAlbumIdFetcher : public QObject, public ThreadWeaver::Job { Q_OBJECT public: Mp3tunesTrackWithAlbumIdFetcher( Mp3tunesLocker * locker, int albumId ); ~Mp3tunesTrackWithAlbumIdFetcher(); - void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) Q_DECL_OVERRIDE; + void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) override; 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 tracksFetched( QList ); private Q_SLOTS: void completeJob(); private: int m_albumId; Mp3tunesLocker* m_locker; QList m_tracks; protected: - void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; + void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; }; /** * Allows for threading the trackWithArtistId fetching process */ class Mp3tunesTrackWithArtistIdFetcher : public QObject, public ThreadWeaver::Job { Q_OBJECT public: Mp3tunesTrackWithArtistIdFetcher( Mp3tunesLocker * locker, int artistId ); ~Mp3tunesTrackWithArtistIdFetcher(); - void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) Q_DECL_OVERRIDE; + void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) override; 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 tracksFetched( QList ); private Q_SLOTS: void completeJob(); private: int m_artistId; Mp3tunesLocker* m_locker; QList m_tracks; protected: - void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; + void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; }; /** * Allows for threading the searching process */ class Mp3tunesSearchMonkey : public QObject, public ThreadWeaver::Job { Q_OBJECT public: Mp3tunesSearchMonkey( Mp3tunesLocker * locker, QString query, int searchFor ); ~Mp3tunesSearchMonkey(); - void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) Q_DECL_OVERRIDE; + void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) override; 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 searchArtistComplete( QList ); void searchAlbumComplete( QList ); void searchTrackComplete( QList ); private Q_SLOTS: void completeJob(); private: QString m_query; int m_searchFor; Mp3tunesLocker* m_locker; Mp3tunesSearchResult m_result; protected: - void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; + void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; }; /** * Allows for threading a track list upload */ class Mp3tunesSimpleUploader : public QObject, public ThreadWeaver::Job { Q_OBJECT public: Mp3tunesSimpleUploader( Mp3tunesLocker * locker, QStringList tracklist ); ~Mp3tunesSimpleUploader(); - void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) Q_DECL_OVERRIDE; + void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) override; 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 uploadComplete(); void incrementProgress(); void endProgressOperation( QObject * ); private Q_SLOTS: void completeJob(); private: Mp3tunesLocker* m_locker; QStringList m_tracklist; protected: - void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; + void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; }; /** * Allows for threading a track from filekey job. */ class Mp3tunesTrackFromFileKeyFetcher : public QObject, public ThreadWeaver::Job { Q_OBJECT public: Mp3tunesTrackFromFileKeyFetcher( Mp3tunesLocker * locker, QString filekey ); ~Mp3tunesTrackFromFileKeyFetcher(); - void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) Q_DECL_OVERRIDE; + void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) override; 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 trackFetched( Mp3tunesLockerTrack &track ); private Q_SLOTS: void completeJob(); private: Mp3tunesLocker* m_locker; Mp3tunesLockerTrack m_track; QString m_filekey; protected: - void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; + void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; }; #endif diff --git a/src/statsyncing/jobs/MatchTracksJob.h b/src/statsyncing/jobs/MatchTracksJob.h index e28f098f26..59d94225b4 100644 --- a/src/statsyncing/jobs/MatchTracksJob.h +++ b/src/statsyncing/jobs/MatchTracksJob.h @@ -1,168 +1,168 @@ /**************************************************************************************** * 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_MATCHTRACKSJOB_H #define STATSYNCING_MATCHTRACKSJOB_H #include "statsyncing/Provider.h" #include "statsyncing/TrackTuple.h" #include #include namespace StatSyncing { /** * Threadweaver job that matches tracks of multiple Providers. * Because comparisonFields() needs to be static, only one instance of this class is * allowed to exist at given time. */ class MatchTracksJob :public QObject, public ThreadWeaver::Job { Q_OBJECT public: explicit MatchTracksJob( const ProviderPtrList &providers, QObject *parent = 0 ); - virtual bool success() const; + virtual bool success() const override; /** * Binary OR of MetaValues.h Meta::val* flags that are used to compare tracks * from different providers. Guaranteed to contain at least artist, album, * title. Valid only after run() has been called. */ static qint64 comparisonFields(); /** * Return a list of providers participating in the matching */ ProviderPtrList providers() const; // results: const QList &matchedTuples() const { return m_matchedTuples; } const PerProviderTrackList &uniqueTracks() const { return m_uniqueTracks; } const PerProviderTrackList &excludedTracks() const { return m_excludedTracks; } const TrackList &tracksToScrobble() const { return m_tracksToScrobble; } public Q_SLOTS: /** * Abort the job as soon as possible. */ void abort(); Q_SIGNALS: /** * Emitted when matcher gets to know total number of steps it will take to * match all tracks. */ void totalSteps( int steps ); /** * Emitted when one progress step has been finished. */ void incrementProgress(); /** * Emitted from worker thread when all time-consuming operations are done. */ void endProgressOperation( QObject *owner ); /** 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); protected: - void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) Q_DECL_OVERRIDE; + void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) override; private: /** * Queries each provider from @param artistProviders for tracks from artist * they specify and separates them into m_uniqueTracks, m_excludedTracks and * m_matchedTuples. */ void matchTracksFromArtist( const QMultiMap &artistProviders ); /** * Finds the "smallest" track among provider track lists; assumes individual * lists are already sorted and non-empty */ TrackPtr findSmallestTrack( const PerProviderTrackList &providerTracks ); /** * Takes tracks from each provider that are equal to @param track. * If a list from @param delegateTracks becomes empty, whole entry for that * provider is removed from @param delegateTracks. */ PerProviderTrackList takeTracksEqualTo( const TrackPtr &track, PerProviderTrackList &providerTracks ); /** * Adds @param tuple to m_matchedTuples and updated m_matchedTrackCounts */ void addMatchedTuple( const TrackTuple &tuple ); /** * Scan for tracks in @param trackList eligible for scrobbling and add them to * m_tracksToScrobble. */ void scanForScrobblableTracks( const TrackList &trackList ); /** * Must be static because comparisonFields needs to be static. * @see comparisonFields() */ static qint64 s_comparisonFields; bool m_abort; ProviderPtrList m_providers; /** * Per-provider list of tracks that are unique to that provider */ PerProviderTrackList m_uniqueTracks; /** * Per-provider list of tracks that have been excluded from synchronization * for various reasons, e.g. for being duplicate within that provider */ PerProviderTrackList m_excludedTracks; /** * Our raison d'etre: tuples of matched tracks */ QList m_matchedTuples; /** * Tracks with non-zero recent playCount, eligible for being scrobbled. */ TrackList m_tracksToScrobble; /** * Per-provider count of matched tracks */ QMap m_matchedTrackCounts; }; } // namespace StatSyncing #endif // STATSYNCING_MATCHTRACKSJOB_H diff --git a/src/statsyncing/jobs/SynchronizeTracksJob.h b/src/statsyncing/jobs/SynchronizeTracksJob.h index 2298b1b0fc..71d5219eb0 100644 --- a/src/statsyncing/jobs/SynchronizeTracksJob.h +++ b/src/statsyncing/jobs/SynchronizeTracksJob.h @@ -1,115 +1,115 @@ /**************************************************************************************** * 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_SYNCHRONIZETRACKSJOB_H #define STATSYNCING_SYNCHRONIZETRACKSJOB_H #include "core/meta/forward_declarations.h" #include "statsyncing/Options.h" #include "statsyncing/ScrobblingService.h" #include "statsyncing/Track.h" #include #include namespace StatSyncing { class TrackTuple; /** * A job to call TrackTuple::synchronize() in order not to make delays in the main * loop. */ class SynchronizeTracksJob : public QObject, public ThreadWeaver::Job { Q_OBJECT public: explicit SynchronizeTracksJob( const QList &tuples, const TrackList &trackToScrobble, const Options &options, QObject *parent = 0 ); /** * Return count of tracks that were updated during synchronization */ int updatedTracksCount() const; /** * Return scrobble counts per scrobbling service and their status. */ QMap > scrobbles(); public Q_SLOTS: /** * Abort the job as soon as possible. */ void abort(); Q_SIGNALS: /** * Emitted when matcher gets to know total number of steps it will take to * match all tracks. */ void totalSteps( int steps ); /** * Emitted when one progress step has been finished. */ void incrementProgress(); /** * Emitted from worker thread when all time-consuming operations are done. */ void endProgressOperation( QObject *owner ); /** * Helper to cross thread boundary between this worker thread and main thread * where StatSyncing::Controller lives. */ void scrobble( const Meta::TrackPtr &track, double playedFraction, const QDateTime &time ); /** 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); protected: - void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) Q_DECL_OVERRIDE; - void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) Q_DECL_OVERRIDE; + void defaultBegin(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void defaultEnd(const ThreadWeaver::JobPointer& job, ThreadWeaver::Thread *thread) override; + void run(ThreadWeaver::JobPointer self = QSharedPointer(), ThreadWeaver::Thread *thread = 0) override; private Q_SLOTS: void slotTrackScrobbled( const ScrobblingServicePtr &service, const Meta::TrackPtr &track ); void slotScrobbleFailed( const ScrobblingServicePtr &service, const Meta::TrackPtr &track, int error ); private: bool m_abort; QList m_tuples; TrackList m_tracksToScrobble; QSet m_scrobbledTracks; QMap > m_scrobbles; int m_updatedTracksCount; const Options m_options; }; } // namespace StatSyncing #endif // STATSYNCING_SYNCHRONIZETRACKSJOB_H diff --git a/src/widgets/BoxWidget.h b/src/widgets/BoxWidget.h index 89b9c68487..77b6509c0c 100644 --- a/src/widgets/BoxWidget.h +++ b/src/widgets/BoxWidget.h @@ -1,51 +1,51 @@ /* * Copyright (C) 2017 Malte Veerman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 BOXWIDGET_H #define BOXWIDGET_H #include "amarok_export.h" #include class QBoxLayout; /** * A Simple QFrame subclass that automatically adds new children to its layout */ class AMAROK_EXPORT BoxWidget : public QFrame { Q_OBJECT public: /** * Constructor * @param vertical defines if this BoxWidget has a vertical Layout. * Has a horizontal if false. */ explicit BoxWidget( bool vertical = true, QWidget *parent = Q_NULLPTR ); virtual ~BoxWidget() {} QBoxLayout* layout() const; protected: - virtual void childEvent(QChildEvent* event) Q_DECL_OVERRIDE; + virtual void childEvent(QChildEvent* event) override; }; #endif // BOXWIDGET_H diff --git a/src/widgets/PrettyTreeRoles.h b/src/widgets/PrettyTreeRoles.h index 89eab0d611..9e189e424b 100644 --- a/src/widgets/PrettyTreeRoles.h +++ b/src/widgets/PrettyTreeRoles.h @@ -1,48 +1,50 @@ /**************************************************************************************** * Copyright (c) 2013 Ralf Engels * * * * This program is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License as published by the Free Software * * Foundation; either version 2 of the License, or (at your option) any later * * version. * * * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ #ifndef AMAROK_PRETTY_TREE_ROLES_H #define AMAROK_PRETTY_TREE_ROLES_H namespace PrettyTreeRoles { /** Roles used for the PrettyTreeDelegate and some models. The numbers start at the strange index to reduce the possibility that different roles from different models clash. */ enum CustomRolesId { SortRole = Qt::UserRole + 51, FilterRole = Qt::UserRole + 52, ByLineRole = Qt::UserRole + 53, /** Boolean value whether given collection knows about used and total capacity */ HasCapacityRole = Qt::UserRole + 54, /** Number of bytes used by music and other files in collection (float) */ UsedCapacityRole = Qt::UserRole + 55, /** Total capacity of the collection in bytes (float) */ TotalCapacityRole = Qt::UserRole + 56, /** The number of collection actions */ DecoratorRoleCount = Qt::UserRole + 57, /** The collection actions */ DecoratorRole = Qt::UserRole + 58, /** True if the item has a cover that should be displayed */ - HasCoverRole = Qt::UserRole + 59 + HasCoverRole = Qt::UserRole + 59, + + YearRole = Qt::UserRole + 60 }; } #endif